426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
218 lines
7.2 KiB
TypeScript
218 lines
7.2 KiB
TypeScript
/**
|
|
* Integration tests for the Ntfy push notification client.
|
|
*
|
|
* Live tests are gated on NTFY_BASE_URL being set. When the env var is absent
|
|
* the suite skips rather than failing — consistent with the credential-gated
|
|
* pattern used across this test directory.
|
|
*
|
|
* Unit-level tests (config parsing, error paths) always run.
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
* AlwaysSkipped in CI
|
|
* ---------------------------------------------------------------------------
|
|
* The `sendPush — live Ntfy` describe block at the bottom of this file is
|
|
* `describe.skipIf(!LIVE_BASE_URL)`. NTFY_BASE_URL is not configured in any
|
|
* GitHub Actions workflow (see .github/workflows/*.yml — no match for
|
|
* NTFY_BASE_URL), so the live block is always skipped in CI today. It is
|
|
* kept here to document the live contract and to run locally when a
|
|
* developer points the suite at a real Ntfy server.
|
|
*
|
|
* To run locally:
|
|
* NTFY_BASE_URL=https://ntfy.sh \
|
|
* NTFY_DEFAULT_TOPIC=eliza-test \
|
|
* bunx vitest run plugins/plugin-personal-assistant/test/notifications-push.integration.test.ts
|
|
*
|
|
* Do NOT enable this suite in CI by injecting NTFY_BASE_URL — it publishes
|
|
* real notifications to a public broker. If we want CI coverage for the
|
|
* HTTP layer, convert this into an offline harness with a local HTTP
|
|
* server stub (nock / msw / `http.createServer`) in a follow-up.
|
|
*/
|
|
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import {
|
|
NtfyConfigError,
|
|
readNtfyConfigFromEnv,
|
|
type SendPushRequest,
|
|
sendPush,
|
|
} from "../src/lifeops/notifications-push.js";
|
|
|
|
const ORIGINAL_ENV = { ...process.env };
|
|
|
|
beforeEach(() => {
|
|
delete process.env.NTFY_BASE_URL;
|
|
delete process.env.NTFY_DEFAULT_TOPIC;
|
|
});
|
|
|
|
afterEach(() => {
|
|
Object.assign(process.env, ORIGINAL_ENV);
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Config parsing — always runs
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("readNtfyConfigFromEnv", () => {
|
|
it("throws NtfyConfigError when NTFY_BASE_URL is absent", () => {
|
|
expect(() => readNtfyConfigFromEnv()).toThrow(NtfyConfigError);
|
|
expect(() => readNtfyConfigFromEnv()).toThrow(/NTFY_BASE_URL/);
|
|
});
|
|
|
|
it("strips trailing slash from baseUrl", () => {
|
|
process.env.NTFY_BASE_URL = "https://ntfy.sh/";
|
|
const config = readNtfyConfigFromEnv();
|
|
expect(config.baseUrl).toBe("https://ntfy.sh");
|
|
});
|
|
|
|
it("uses NTFY_DEFAULT_TOPIC when set", () => {
|
|
process.env.NTFY_BASE_URL = "https://ntfy.sh";
|
|
process.env.NTFY_DEFAULT_TOPIC = "my-alerts";
|
|
const config = readNtfyConfigFromEnv();
|
|
expect(config.defaultTopic).toBe("my-alerts");
|
|
});
|
|
|
|
it("falls back to 'eliza' when NTFY_DEFAULT_TOPIC is not set", () => {
|
|
process.env.NTFY_BASE_URL = "https://ntfy.sh";
|
|
const config = readNtfyConfigFromEnv();
|
|
expect(config.defaultTopic).toBe("eliza");
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// sendPush error path — always runs (no network)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("sendPush — config error", () => {
|
|
it("throws NtfyConfigError when no config passed and env is empty", async () => {
|
|
await expect(sendPush({ title: "Test", message: "Hello" })).rejects.toThrow(
|
|
NtfyConfigError,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("sendPush — network error handling", () => {
|
|
it("throws Error on fetch rejection", async () => {
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn().mockRejectedValue(new TypeError("Failed to fetch")),
|
|
);
|
|
process.env.NTFY_BASE_URL = "https://ntfy.example.invalid";
|
|
|
|
await expect(sendPush({ title: "Test", message: "Hello" })).rejects.toThrow(
|
|
"Failed to fetch",
|
|
);
|
|
});
|
|
|
|
it("throws Error on non-OK HTTP response", async () => {
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
status: 403,
|
|
text: async () => "Forbidden",
|
|
}),
|
|
);
|
|
process.env.NTFY_BASE_URL = "https://ntfy.example.invalid";
|
|
|
|
await expect(sendPush({ title: "Test", message: "Hello" })).rejects.toThrow(
|
|
"403",
|
|
);
|
|
});
|
|
|
|
it("returns messageId and deliveredAt on success", async () => {
|
|
const fakeId = "abc123";
|
|
const fakeTime = Math.floor(Date.now() / 1000);
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ id: fakeId, time: fakeTime }),
|
|
}),
|
|
);
|
|
process.env.NTFY_BASE_URL = "https://ntfy.example.invalid";
|
|
|
|
const result = await sendPush({ title: "Test", message: "Hello" });
|
|
expect(result.messageId).toBe(fakeId);
|
|
expect(result.deliveredAt).toBe(new Date(fakeTime * 1000).toISOString());
|
|
});
|
|
|
|
it("uses topic from request over default", async () => {
|
|
let capturedUrl = "";
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn().mockImplementation((url: string) => {
|
|
capturedUrl = url;
|
|
return Promise.resolve({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ id: "x", time: Math.floor(Date.now() / 1000) }),
|
|
});
|
|
}),
|
|
);
|
|
process.env.NTFY_BASE_URL = "https://ntfy.example.invalid";
|
|
process.env.NTFY_DEFAULT_TOPIC = "default-topic";
|
|
|
|
await sendPush({ topic: "custom-topic", title: "Test", message: "Hello" });
|
|
expect(capturedUrl).toContain("/custom-topic");
|
|
});
|
|
|
|
it("forwards click, tags, and normalized priority headers", async () => {
|
|
let capturedInit: RequestInit | undefined;
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn().mockImplementation((_url: string, init?: RequestInit) => {
|
|
capturedInit = init;
|
|
return Promise.resolve({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ id: "x", time: Math.floor(Date.now() / 1000) }),
|
|
});
|
|
}),
|
|
);
|
|
process.env.NTFY_BASE_URL = "https://ntfy.example.invalid";
|
|
|
|
await sendPush({
|
|
title: "Meeting ladder",
|
|
message: "Board meeting starts in 10 minutes.",
|
|
priority: 5,
|
|
tags: ["calendar", "alarm_clock"],
|
|
click: "eliza://meeting/board-123",
|
|
});
|
|
|
|
expect(capturedInit?.headers).toMatchObject({
|
|
Title: "Meeting ladder",
|
|
Priority: "5",
|
|
Tags: "calendar,alarm_clock",
|
|
Click: "eliza://meeting/board-123",
|
|
});
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Live integration — gated on NTFY_BASE_URL
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const LIVE_BASE_URL = ORIGINAL_ENV.NTFY_BASE_URL;
|
|
|
|
describe.skipIf(!LIVE_BASE_URL)("sendPush — live Ntfy", () => {
|
|
it("publishes a notification and returns a messageId", async () => {
|
|
const request: SendPushRequest = {
|
|
title: "LifeOps integration test",
|
|
message:
|
|
"This is a test push from the notifications-push integration test suite.",
|
|
priority: 3,
|
|
tags: ["white_check_mark"],
|
|
};
|
|
|
|
const result = await sendPush(request, {
|
|
baseUrl: LIVE_BASE_URL?.replace(/\/$/, ""),
|
|
defaultTopic: ORIGINAL_ENV.NTFY_DEFAULT_TOPIC ?? "eliza-test",
|
|
});
|
|
|
|
expect(typeof result.messageId).toBe("string");
|
|
expect(result.messageId.length).toBeGreaterThan(0);
|
|
expect(typeof result.deliveredAt).toBe("string");
|
|
});
|
|
});
|