Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

199 lines
6.9 KiB
TypeScript

/**
* Real-DB integration tests for the blocker `app_blocker` schema.
*
* IMPORTANT — what this test does NOT do, and why:
* The blocker plugin's two services (`WebsiteBlockerService`,
* `AppBlockerService`) drive the SelfControl hosts-file engine and native
* mobile OS app-blocking; their state lives in the hosts file and in runtime
* `Task` records, NOT in the `app_blocker` schema. No service or repository in
* this plugin reads or writes `block_rules` / `active_sessions` / `allow_list`
* (the drizzle schema is shipped + migrated, but there is no rule-persistence
* code path reachable without real native blocking). There is therefore no
* service- or repository-level rule round-trip to exercise — per the task's
* gate #4, we test the persistent store directly against the live DB instead.
*
* This suite boots a REAL PGLite-backed AgentRuntime via
* {@link createRealTestRuntime} with `blockerPlugin` registered, so the SQL
* plugin's migration runner materializes the `app_blocker` tables from the
* plugin `schema` field. It then drives the plugin's own drizzle table
* definitions (`blockRulesTable` / `activeSessionsTable` / `allowListTable`)
* through the live `runtime.db` handle: every assertion is an insert-then-
* read-back round-trip against the real PGLite database — proving the schema
* migrates and the column/type mapping is correct against a real Postgres
* engine, not a mocked adapter.
*
* The hosts-file engine is kept away from `/etc/hosts` by the temp-hosts env
* the real-runtime helper sets (SELFCONTROL_HOSTS_FILE_PATH /
* WEBSITE_BLOCKER_HOSTS_FILE_PATH).
*
* Hermetic: no network, no native OS APIs, no /etc/hosts writes.
*/
import type { AgentRuntime, UUID } from "@elizaos/core";
import { and, eq } from "drizzle-orm";
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import {
createRealTestRuntime,
type RealTestRuntimeResult,
} from "../../../packages/test/helpers/real-runtime.ts";
import {
activeSessionsTable,
allowListTable,
blockRulesTable,
} from "../src/db/schema.ts";
import blockerPlugin from "../src/index.ts";
const ENTITY_ID = "11111111-1111-4111-8111-111111111111" as UUID;
describe("app_blocker schema — real PGLite", () => {
let runtime: AgentRuntime;
let testResult: RealTestRuntimeResult;
let db: NodePgDatabase;
beforeAll(async () => {
testResult = await createRealTestRuntime({
characterName: "blocker-real-db-tests",
// Registering the plugin makes runtime.initialize() run the SQL plugin's
// migration for the `app_blocker` schema (the plugin `schema` field).
plugins: [blockerPlugin],
});
runtime = testResult.runtime;
db = runtime.db as NodePgDatabase;
if (!db) {
throw new Error("runtime.db unavailable — plugin-sql did not initialize");
}
}, 180_000);
afterAll(async () => {
await testResult?.cleanup();
});
it("persists a block rule and reads it back from the live DB", async () => {
const [inserted] = await db
.insert(blockRulesTable)
.values({
agentId: runtime.agentId as UUID,
entityId: ENTITY_ID,
target: "website",
pattern: "reddit.com",
notes: "deep-work block",
metadata: { source: "real-db-test" },
})
.returning();
expect(inserted?.id).toBeTruthy();
expect(inserted?.pattern).toBe("reddit.com");
// Round-trip: the row is really in the DB, queried by (entity, target).
const rows = await db
.select()
.from(blockRulesTable)
.where(
and(
eq(blockRulesTable.entityId, ENTITY_ID),
eq(blockRulesTable.target, "website"),
),
);
const fetched = rows.find((r) => r.id === inserted?.id);
expect(fetched).toBeTruthy();
expect(fetched?.pattern).toBe("reddit.com");
expect(fetched?.notes).toBe("deep-work block");
expect(fetched?.metadata).toEqual({ source: "real-db-test" });
expect(fetched?.agentId).toBe(runtime.agentId);
expect(fetched?.createdAt).toBeInstanceOf(Date);
});
it("persists an active session and reads it back from the live DB", async () => {
const endsAt = new Date(Date.now() + 60 * 60 * 1000);
const [session] = await db
.insert(activeSessionsTable)
.values({
agentId: runtime.agentId as UUID,
entityId: ENTITY_ID,
target: "website",
status: "active",
rules: ["reddit.com", "news.ycombinator.com"],
metadata: { reason: "focus" },
endsAt,
})
.returning();
expect(session?.id).toBeTruthy();
const active = await db
.select()
.from(activeSessionsTable)
.where(
and(
eq(activeSessionsTable.entityId, ENTITY_ID),
eq(activeSessionsTable.status, "active"),
),
);
const fetched = active.find((s) => s.id === session?.id);
expect(fetched).toBeTruthy();
expect(fetched?.status).toBe("active");
expect(fetched?.rules).toEqual(["reddit.com", "news.ycombinator.com"]);
expect(fetched?.endsAt).toBeInstanceOf(Date);
// Mutate (end the session) and confirm the UPDATE landed in the live DB.
const endedAt = new Date();
await db
.update(activeSessionsTable)
.set({ status: "ended", endedAt })
.where(eq(activeSessionsTable.id, session?.id as UUID));
const [reread] = await db
.select()
.from(activeSessionsTable)
.where(eq(activeSessionsTable.id, session?.id as UUID));
expect(reread?.status).toBe("ended");
expect(reread?.endedAt).toBeInstanceOf(Date);
});
it("persists an allow-list entry and reads it back from the live DB", async () => {
const [entry] = await db
.insert(allowListTable)
.values({
agentId: runtime.agentId as UUID,
entityId: ENTITY_ID,
target: "website",
pattern: "docs.elizaos.ai",
reason: "work resource",
})
.returning();
expect(entry?.id).toBeTruthy();
const rows = await db
.select()
.from(allowListTable)
.where(eq(allowListTable.entityId, ENTITY_ID));
const fetched = rows.find((r) => r.id === entry?.id);
expect(fetched).toBeTruthy();
expect(fetched?.pattern).toBe("docs.elizaos.ai");
expect(fetched?.reason).toBe("work resource");
});
it("deletes a block rule from the live DB", async () => {
const [rule] = await db
.insert(blockRulesTable)
.values({
agentId: runtime.agentId as UUID,
entityId: ENTITY_ID,
target: "app",
pattern: "com.example.game",
})
.returning();
expect(rule?.id).toBeTruthy();
const deleted = await db
.delete(blockRulesTable)
.where(eq(blockRulesTable.id, rule?.id as UUID))
.returning({ id: blockRulesTable.id });
expect(deleted).toHaveLength(1);
const after = await db
.select()
.from(blockRulesTable)
.where(eq(blockRulesTable.id, rule?.id as UUID));
expect(after).toHaveLength(0);
});
});