mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
test(audit): consolidate worker lifecycle coverage (#122369)
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
committed by
GitHub
parent
8876528f7c
commit
b529217878
@@ -78,16 +78,34 @@ afterEach(() => {
|
||||
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
||||
|
||||
describe("audit event worker", () => {
|
||||
it("keeps first-use identity storage absent during maintenance without admission", async () => {
|
||||
it("keeps fresh storage identity-free when recovery evidence is missing", async () => {
|
||||
const stateDir = tempDirs.make("openclaw-audit-writer-");
|
||||
const database = { env: { OPENCLAW_STATE_DIR: stateDir } };
|
||||
const errors: string[] = [];
|
||||
const writer = createAuditEventWriter({ stateDir, onError: (error) => errors.push(error) });
|
||||
|
||||
await writer.ready;
|
||||
expect(
|
||||
openOpenClawStateDatabase(database)
|
||||
.db.prepare("SELECT name FROM sqlite_schema WHERE type = 'table' AND name = ?")
|
||||
.get("execution_identity_contexts"),
|
||||
).toBeUndefined();
|
||||
expect(writer.record(input())).toBe(true);
|
||||
const token = createExecutionIdentityAdmissionToken("raw-run-not-a-secret", {
|
||||
contextId: "context-missing",
|
||||
executionId: "execution-missing",
|
||||
now: 100,
|
||||
});
|
||||
const startedAt = performance.now();
|
||||
expect(writer.recordExecutionIdentity({ kind: "retry-reference", token })).toBe(true);
|
||||
expect(performance.now() - startedAt).toBeLessThan(250);
|
||||
await writer.stop();
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
expect(errors).toEqual(["audit execution identity recovery evidence unavailable"]);
|
||||
expect(JSON.stringify(errors)).not.toContain(token.contextId);
|
||||
expect(JSON.stringify(errors)).not.toContain(token.executionId);
|
||||
expect(JSON.stringify(errors)).not.toContain(token.runId);
|
||||
expect(listAuditEvents({ database, limit: 10 }).events).toHaveLength(1);
|
||||
expect(
|
||||
openOpenClawStateDatabase(database)
|
||||
.db.prepare("SELECT name FROM sqlite_schema WHERE type = 'table' AND name = ?")
|
||||
@@ -95,95 +113,90 @@ describe("audit event worker", () => {
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps an established current store identity-free during maintenance", async () => {
|
||||
it("keeps the shared queue nonblocking under a held write lock and flushes before stop", async () => {
|
||||
const stateDir = tempDirs.make("openclaw-audit-writer-");
|
||||
const database = { env: { OPENCLAW_STATE_DIR: stateDir } };
|
||||
recordAuditEvent(input(), database);
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
const errors: string[] = [];
|
||||
const writer = createAuditEventWriter({ stateDir, onError: (error) => errors.push(error) });
|
||||
|
||||
const writer = createAuditEventWriter({
|
||||
stateDir,
|
||||
maxPending: 2,
|
||||
onError: (error) => errors.push(error),
|
||||
});
|
||||
await writer.ready;
|
||||
await writer.stop();
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
expect(listAuditEvents({ database, limit: 10 }).events).toHaveLength(1);
|
||||
const { db } = openOpenClawStateDatabase(database);
|
||||
expect(
|
||||
openOpenClawStateDatabase(database)
|
||||
.db.prepare("SELECT name FROM sqlite_schema WHERE type = 'table' AND name = ?")
|
||||
db
|
||||
.prepare("SELECT name FROM sqlite_schema WHERE type = 'table' AND name = ?")
|
||||
.get("execution_identity_contexts"),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns immediately under SQLite contention and flushes before stop", async () => {
|
||||
const stateDir = tempDirs.make("openclaw-audit-writer-");
|
||||
const database = { env: { OPENCLAW_STATE_DIR: stateDir } };
|
||||
const errors: string[] = [];
|
||||
const writer = createAuditEventWriter({ stateDir, onError: (error) => errors.push(error) });
|
||||
await writer.ready;
|
||||
const { db } = openOpenClawStateDatabase(database);
|
||||
db.exec("BEGIN IMMEDIATE");
|
||||
const startedAt = performance.now();
|
||||
expect(writer.record(input())).toBe(true);
|
||||
expect(performance.now() - startedAt).toBeLessThan(250);
|
||||
db.exec("ROLLBACK");
|
||||
|
||||
await writer.stop();
|
||||
expect(errors).toEqual([]);
|
||||
expect(listAuditEvents({ database, limit: 10 }).events).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("keeps first-use identity admission prompt under a held write lock", async () => {
|
||||
const stateDir = tempDirs.make("openclaw-audit-writer-");
|
||||
const database = { env: { OPENCLAW_STATE_DIR: stateDir } };
|
||||
const { db } = openOpenClawStateDatabase(database);
|
||||
db.exec("DELETE FROM audit_identity_keys;");
|
||||
db.exec("BEGIN IMMEDIATE");
|
||||
const errors: string[] = [];
|
||||
const writer = createAuditEventWriter({ stateDir, onError: (error) => errors.push(error) });
|
||||
const clearSink = configureExecutionIdentityAdmissionSink(writer.recordExecutionIdentity);
|
||||
const admittedAt = Date.now();
|
||||
|
||||
const startedAt = performance.now();
|
||||
expect(
|
||||
enqueueExecutionIdentityContextAtAdmission(
|
||||
{
|
||||
runId: "held-lock-run",
|
||||
agentId: "main",
|
||||
ingress: {
|
||||
kind: "local-cli",
|
||||
boundary: "agent-command.local",
|
||||
state: "present",
|
||||
rawSourceRef: "raw-ingress-secret",
|
||||
try {
|
||||
const startedAt = performance.now();
|
||||
expect(writer.record({ ...input(), sourceId: "run-2:1:started", runId: "run-2" })).toBe(true);
|
||||
expect(
|
||||
enqueueExecutionIdentityContextAtAdmission(
|
||||
{
|
||||
runId: "held-lock-run",
|
||||
agentId: "main",
|
||||
ingress: {
|
||||
kind: "local-cli",
|
||||
boundary: "agent-command.local",
|
||||
state: "present",
|
||||
rawSourceRef: "raw-ingress-secret",
|
||||
},
|
||||
runtime: { kind: "embedded" },
|
||||
invoker: { kind: "local-account", rawPrincipalRef: "raw-principal-secret" },
|
||||
},
|
||||
runtime: { kind: "embedded" },
|
||||
invoker: { kind: "local-account", rawPrincipalRef: "raw-principal-secret" },
|
||||
},
|
||||
{
|
||||
enabled: true,
|
||||
contextId: "held-lock-context",
|
||||
executionId: "held-lock-execution",
|
||||
now: admittedAt,
|
||||
runtimeInstanceId: "raw-runtime-secret",
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
candidateContextId: "held-lock-context",
|
||||
candidateExecutionId: "held-lock-execution",
|
||||
accepted: true,
|
||||
});
|
||||
expect(performance.now() - startedAt).toBeLessThan(250);
|
||||
expect(
|
||||
db.prepare("SELECT name FROM sqlite_schema WHERE name = 'execution_identity_contexts'").get(),
|
||||
).toBeUndefined();
|
||||
expect(db.prepare("SELECT COUNT(*) AS count FROM audit_identity_keys").get()).toEqual({
|
||||
count: 0,
|
||||
});
|
||||
{
|
||||
enabled: true,
|
||||
contextId: "held-lock-context",
|
||||
executionId: "held-lock-execution",
|
||||
now: admittedAt,
|
||||
runtimeInstanceId: "raw-runtime-secret",
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
candidateContextId: "held-lock-context",
|
||||
candidateExecutionId: "held-lock-execution",
|
||||
accepted: true,
|
||||
});
|
||||
expect(performance.now() - startedAt).toBeLessThan(250);
|
||||
expect(
|
||||
writer.recordExecutionIdentity({
|
||||
kind: "retry-reference",
|
||||
token: createExecutionIdentityAdmissionToken("queue-full-run", {
|
||||
contextId: "queue-full-context",
|
||||
executionId: "queue-full-execution",
|
||||
now: admittedAt,
|
||||
}),
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(errors).toEqual(["audit event queue is full (2); dropping metadata"]);
|
||||
expect(
|
||||
db
|
||||
.prepare("SELECT name FROM sqlite_schema WHERE name = 'execution_identity_contexts'")
|
||||
.get(),
|
||||
).toBeUndefined();
|
||||
expect(db.prepare("SELECT COUNT(*) AS count FROM audit_identity_keys").get()).toEqual({
|
||||
count: 0,
|
||||
});
|
||||
} finally {
|
||||
try {
|
||||
db.exec("ROLLBACK");
|
||||
} finally {
|
||||
clearSink();
|
||||
await writer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
db.exec("ROLLBACK");
|
||||
clearSink();
|
||||
await writer.stop();
|
||||
expect(errors).toEqual([]);
|
||||
expect(errors).toEqual(["audit event queue is full (2); dropping metadata"]);
|
||||
expect(listAuditEvents({ database, limit: 10 }).events).toHaveLength(2);
|
||||
expect(
|
||||
inspectExecutionIdentityRun({ runId: "held-lock-run" }, { ...database, now: admittedAt }),
|
||||
).toMatchObject({
|
||||
@@ -212,7 +225,7 @@ describe("audit event worker", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("prunes expired identity contexts at startup without a new run", async () => {
|
||||
it("prunes expired identity contexts before preserving exact-envelope conflicts", async () => {
|
||||
const stateDir = tempDirs.make("openclaw-audit-writer-");
|
||||
const database = { env: { OPENCLAW_STATE_DIR: stateDir } };
|
||||
persistExecutionIdentityAdmissionEnvelope(
|
||||
@@ -232,54 +245,11 @@ describe("audit event worker", () => {
|
||||
const errors: string[] = [];
|
||||
const writer = createAuditEventWriter({ stateDir, onError: (error) => errors.push(error) });
|
||||
await writer.ready;
|
||||
|
||||
expect(
|
||||
openOpenClawStateDatabase(database)
|
||||
.db.prepare("SELECT COUNT(*) AS count FROM execution_identity_contexts")
|
||||
.get(),
|
||||
).toEqual({ count: 0 });
|
||||
await writer.stop();
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it("uses one pending limit across audit events and identity envelopes", async () => {
|
||||
const stateDir = tempDirs.make("openclaw-audit-writer-");
|
||||
const database = { env: { OPENCLAW_STATE_DIR: stateDir } };
|
||||
const { db } = openOpenClawStateDatabase(database);
|
||||
db.exec("BEGIN IMMEDIATE");
|
||||
const errors: string[] = [];
|
||||
const writer = createAuditEventWriter({
|
||||
stateDir,
|
||||
maxPending: 1,
|
||||
onError: (error) => errors.push(error),
|
||||
});
|
||||
expect(writer.record(input())).toBe(true);
|
||||
expect(
|
||||
writer.recordExecutionIdentity(
|
||||
captureWork(
|
||||
captureExecutionIdentityAdmissionEnvelope(
|
||||
{
|
||||
runId: "queue-full-run",
|
||||
agentId: "main",
|
||||
ingress: { kind: "local-cli", boundary: "agent-command.local" },
|
||||
runtime: { kind: "embedded" },
|
||||
},
|
||||
{ runtimeInstanceId: "runtime-1" },
|
||||
),
|
||||
),
|
||||
),
|
||||
).toBe(false);
|
||||
expect(errors).toContain("audit event queue is full (1); dropping metadata");
|
||||
db.exec("ROLLBACK");
|
||||
await writer.stop();
|
||||
expect(listAuditEvents({ database, limit: 10 }).events).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("preserves exact-envelope idempotency and safely reports every canonical conflict", async () => {
|
||||
const stateDir = tempDirs.make("openclaw-audit-writer-");
|
||||
const database = { env: { OPENCLAW_STATE_DIR: stateDir } };
|
||||
const errors: string[] = [];
|
||||
const writer = createAuditEventWriter({ stateDir, onError: (error) => errors.push(error) });
|
||||
const admittedAt = Date.now();
|
||||
const original = captureExecutionIdentityAdmissionEnvelope(
|
||||
{
|
||||
@@ -363,32 +333,6 @@ describe("audit event worker", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("reports a lost durable recovery reference safely without blocking the caller", async () => {
|
||||
const stateDir = tempDirs.make("openclaw-audit-writer-");
|
||||
const errors: string[] = [];
|
||||
const writer = createAuditEventWriter({ stateDir, onError: (error) => errors.push(error) });
|
||||
const token = createExecutionIdentityAdmissionToken("raw-run-not-a-secret", {
|
||||
contextId: "context-missing",
|
||||
executionId: "execution-missing",
|
||||
now: 100,
|
||||
});
|
||||
|
||||
const startedAt = performance.now();
|
||||
expect(writer.recordExecutionIdentity({ kind: "retry-reference", token })).toBe(true);
|
||||
expect(performance.now() - startedAt).toBeLessThan(250);
|
||||
await writer.stop();
|
||||
|
||||
expect(errors).toContain("audit execution identity recovery evidence unavailable");
|
||||
expect(JSON.stringify(errors)).not.toContain(token.contextId);
|
||||
expect(JSON.stringify(errors)).not.toContain(token.executionId);
|
||||
expect(JSON.stringify(errors)).not.toContain(token.runId);
|
||||
expect(
|
||||
openOpenClawStateDatabase({ env: { OPENCLAW_STATE_DIR: stateDir } })
|
||||
.db.prepare("SELECT name FROM sqlite_schema WHERE type = 'table' AND name = ?")
|
||||
.get("execution_identity_contexts"),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps unavailable worker, schema, and insert failures off the admission path", async () => {
|
||||
const envelope = captureExecutionIdentityAdmissionEnvelope(
|
||||
{
|
||||
@@ -470,12 +414,29 @@ describe("audit event worker", () => {
|
||||
).toMatchObject({ state: "unknown", reasonCode: "run_not_found" });
|
||||
});
|
||||
|
||||
it("keeps malformed, serialization, key, and persistence failures nonblocking and redaction-safe", async () => {
|
||||
it("keeps malformed, serialization, and key failures nonblocking and redaction-safe", async () => {
|
||||
const stateDir = tempDirs.make("openclaw-audit-writer-");
|
||||
const database = { env: { OPENCLAW_STATE_DIR: stateDir } };
|
||||
const errors: string[] = [];
|
||||
const writer = createAuditEventWriter({ stateDir, onError: (error) => errors.push(error) });
|
||||
const rawSecret = "raw-worker-message-secret";
|
||||
persistExecutionIdentityAdmissionEnvelope(
|
||||
captureExecutionIdentityAdmissionEnvelope(
|
||||
{
|
||||
runId: "before-key-loss",
|
||||
agentId: "main",
|
||||
ingress: { kind: "local-cli", boundary: "agent-command.local" },
|
||||
runtime: { kind: "embedded" },
|
||||
},
|
||||
{ runtimeInstanceId: "runtime-1" },
|
||||
),
|
||||
database,
|
||||
);
|
||||
openOpenClawStateDatabase(database).db.exec("DELETE FROM audit_identity_keys;");
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
const errors: string[] = [];
|
||||
const writer = createAuditEventWriter({
|
||||
stateDir,
|
||||
onError: (error) => errors.push(error),
|
||||
});
|
||||
const unserializable = {
|
||||
...captureExecutionIdentityAdmissionEnvelope(
|
||||
{
|
||||
@@ -494,42 +455,9 @@ describe("audit event worker", () => {
|
||||
},
|
||||
};
|
||||
expect(writer.recordExecutionIdentity(captureWork(unserializable as never))).toBe(false);
|
||||
await writer.stop();
|
||||
expect(errors).toContain("audit execution identity envelope could not be queued");
|
||||
expect(JSON.stringify(errors)).not.toContain(rawSecret);
|
||||
|
||||
const malformedErrors: string[] = [];
|
||||
const malformedWriter = createAuditEventWriter({
|
||||
stateDir,
|
||||
onError: (error) => malformedErrors.push(error),
|
||||
});
|
||||
expect(malformedWriter.recordExecutionIdentity({ rawSecret } as never)).toBe(true);
|
||||
await malformedWriter.stop();
|
||||
expect(malformedErrors).toContain("audit execution identity envelope rejected");
|
||||
expect(JSON.stringify(malformedErrors)).not.toContain(rawSecret);
|
||||
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
persistExecutionIdentityAdmissionEnvelope(
|
||||
captureExecutionIdentityAdmissionEnvelope(
|
||||
{
|
||||
runId: "before-key-loss",
|
||||
agentId: "main",
|
||||
ingress: { kind: "local-cli", boundary: "agent-command.local" },
|
||||
runtime: { kind: "embedded" },
|
||||
},
|
||||
{ runtimeInstanceId: "runtime-1" },
|
||||
),
|
||||
database,
|
||||
);
|
||||
openOpenClawStateDatabase(database).db.exec("DELETE FROM audit_identity_keys;");
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
const keyErrors: string[] = [];
|
||||
const keyWriter = createAuditEventWriter({
|
||||
stateDir,
|
||||
onError: (error) => keyErrors.push(error),
|
||||
});
|
||||
expect(writer.recordExecutionIdentity({ rawSecret } as never)).toBe(true);
|
||||
expect(
|
||||
keyWriter.recordExecutionIdentity(
|
||||
writer.recordExecutionIdentity(
|
||||
captureWork(
|
||||
captureExecutionIdentityAdmissionEnvelope(
|
||||
{
|
||||
@@ -543,9 +471,11 @@ describe("audit event worker", () => {
|
||||
),
|
||||
),
|
||||
).toBe(true);
|
||||
await keyWriter.stop();
|
||||
expect(keyErrors).toContain("audit execution identity key unavailable");
|
||||
expect(JSON.stringify(keyErrors)).not.toContain(rawSecret);
|
||||
await writer.stop();
|
||||
expect(errors).toContain("audit execution identity envelope could not be queued");
|
||||
expect(errors).toContain("audit execution identity envelope rejected");
|
||||
expect(errors).toContain("audit execution identity key unavailable");
|
||||
expect(JSON.stringify(errors)).not.toContain(rawSecret);
|
||||
expect(
|
||||
inspectExecutionIdentityRun({ runId: "after-key-loss" }, database).identity,
|
||||
).toMatchObject({ state: "unknown", reasonCode: "run_not_found" });
|
||||
|
||||
Reference in New Issue
Block a user