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
199 lines
6.9 KiB
TypeScript
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);
|
|
});
|
|
});
|