@kb-labs/adapters 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursorrules +32 -0
- package/.github/workflows/ci.yml +13 -0
- package/.github/workflows/deploy.yml +28 -0
- package/.github/workflows/docker-build.yml +25 -0
- package/.github/workflows/drift-check.yml +10 -0
- package/.github/workflows/profiles-validate.yml +16 -0
- package/.github/workflows/release.yml +8 -0
- package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
- package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
- package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
- package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
- package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
- package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
- package/.kb/devkit/agents/release-manager/context.globs +7 -0
- package/.kb/devkit/agents/release-manager/prompt.md +27 -0
- package/.kb/devkit/agents/release-manager/runbook.md +17 -0
- package/.kb/devkit/agents/test-generator/context.globs +7 -0
- package/.kb/devkit/agents/test-generator/prompt.md +27 -0
- package/.kb/devkit/agents/test-generator/runbook.md +18 -0
- package/CONTRIBUTING.md +90 -0
- package/IMPLEMENTATION_COMPLETE.md +416 -0
- package/LICENSE +186 -0
- package/README-TEMPLATE.md +179 -0
- package/README.md +306 -0
- package/docs/DOCUMENTATION.md +74 -0
- package/docs/adr/0000-template.md +49 -0
- package/docs/adr/0001-architecture-and-repository-layout.md +33 -0
- package/docs/adr/0002-plugins-and-extensibility.md +46 -0
- package/docs/adr/0003-package-and-module-boundaries.md +37 -0
- package/docs/adr/0004-versioning-and-release-policy.md +38 -0
- package/docs/adr/0005-use-devkit-for-shared-tooling.md +48 -0
- package/docs/adr/0006-adopt-devkit-sync.md +47 -0
- package/docs/adr/0007-drift-kit-check.md +72 -0
- package/docs/adr/0008-devkit-sync-wrapper-strategy.md +67 -0
- package/docs/naming-convention.md +272 -0
- package/eslint.config.js +27 -0
- package/kb-labs.config.json +5 -0
- package/package.json +84 -0
- package/package.json.bin +25 -0
- package/package.json.lib +30 -0
- package/packages/adapters-analytics-duckdb/package.json +54 -0
- package/packages/adapters-analytics-duckdb/scripts/migrate-from-jsonl.mjs +253 -0
- package/packages/adapters-analytics-duckdb/src/index.ts +380 -0
- package/packages/adapters-analytics-duckdb/src/manifest.ts +36 -0
- package/packages/adapters-analytics-duckdb/src/schema.ts +161 -0
- package/packages/adapters-analytics-duckdb/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-duckdb/tsconfig.json +9 -0
- package/packages/adapters-analytics-duckdb/tsup.config.ts +9 -0
- package/packages/adapters-analytics-file/README.md +32 -0
- package/packages/adapters-analytics-file/eslint.config.js +27 -0
- package/packages/adapters-analytics-file/package.json +50 -0
- package/packages/adapters-analytics-file/src/__tests__/daily-stats.spec.ts +287 -0
- package/packages/adapters-analytics-file/src/__tests__/scoped-analytics.test.ts +233 -0
- package/packages/adapters-analytics-file/src/index.test.ts +214 -0
- package/packages/adapters-analytics-file/src/index.ts +830 -0
- package/packages/adapters-analytics-file/src/manifest.ts +45 -0
- package/packages/adapters-analytics-file/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-file/tsconfig.json +9 -0
- package/packages/adapters-analytics-file/tsup.config.ts +9 -0
- package/packages/adapters-analytics-sqlite/package.json +55 -0
- package/packages/adapters-analytics-sqlite/scripts/migrate-from-jsonl.mjs +194 -0
- package/packages/adapters-analytics-sqlite/src/index.ts +460 -0
- package/packages/adapters-analytics-sqlite/src/manifest.ts +41 -0
- package/packages/adapters-analytics-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-sqlite/tsconfig.json +9 -0
- package/packages/adapters-analytics-sqlite/tsup.config.ts +9 -0
- package/packages/adapters-environment-docker/README.md +28 -0
- package/packages/adapters-environment-docker/eslint.config.js +5 -0
- package/packages/adapters-environment-docker/package.json +49 -0
- package/packages/adapters-environment-docker/src/index.test.ts +138 -0
- package/packages/adapters-environment-docker/src/index.ts +439 -0
- package/packages/adapters-environment-docker/src/manifest.ts +65 -0
- package/packages/adapters-environment-docker/tsconfig.build.json +15 -0
- package/packages/adapters-environment-docker/tsconfig.json +16 -0
- package/packages/adapters-environment-docker/tsup.config.ts +9 -0
- package/packages/adapters-eventbus-cache/README.md +242 -0
- package/packages/adapters-eventbus-cache/eslint.config.js +27 -0
- package/packages/adapters-eventbus-cache/package.json +46 -0
- package/packages/adapters-eventbus-cache/src/index.test.ts +235 -0
- package/packages/adapters-eventbus-cache/src/index.ts +215 -0
- package/packages/adapters-eventbus-cache/src/manifest.ts +50 -0
- package/packages/adapters-eventbus-cache/src/types.ts +58 -0
- package/packages/adapters-eventbus-cache/tsconfig.build.json +15 -0
- package/packages/adapters-eventbus-cache/tsconfig.json +9 -0
- package/packages/adapters-eventbus-cache/tsup.config.ts +9 -0
- package/packages/adapters-fs/README.md +171 -0
- package/packages/adapters-fs/allowed.txt +1 -0
- package/packages/adapters-fs/conflict.txt +1 -0
- package/packages/adapters-fs/dest.txt +1 -0
- package/packages/adapters-fs/eslint.config.js +27 -0
- package/packages/adapters-fs/exists.txt +1 -0
- package/packages/adapters-fs/not-allowed.txt +1 -0
- package/packages/adapters-fs/other.txt +1 -0
- package/packages/adapters-fs/package.json +55 -0
- package/packages/adapters-fs/public/file1.txt +1 -0
- package/packages/adapters-fs/public/file2.txt +1 -0
- package/packages/adapters-fs/secret.txt +1 -0
- package/packages/adapters-fs/secrets/key.txt +1 -0
- package/packages/adapters-fs/src/index.test.ts +243 -0
- package/packages/adapters-fs/src/index.ts +258 -0
- package/packages/adapters-fs/src/manifest.ts +35 -0
- package/packages/adapters-fs/src/secure-storage.test.ts +380 -0
- package/packages/adapters-fs/src/secure-storage.ts +268 -0
- package/packages/adapters-fs/test.json +1 -0
- package/packages/adapters-fs/test.txt +1 -0
- package/packages/adapters-fs/test.xyz +1 -0
- package/packages/adapters-fs/test1.txt +1 -0
- package/packages/adapters-fs/test2.txt +1 -0
- package/packages/adapters-fs/tsconfig.build.json +15 -0
- package/packages/adapters-fs/tsconfig.json +9 -0
- package/packages/adapters-fs/tsup.config.ts +8 -0
- package/packages/adapters-fs/vitest.config.ts +19 -0
- package/packages/adapters-log-ringbuffer/README.md +228 -0
- package/packages/adapters-log-ringbuffer/eslint.config.js +27 -0
- package/packages/adapters-log-ringbuffer/package.json +47 -0
- package/packages/adapters-log-ringbuffer/src/__tests__/ring-buffer.test.ts +450 -0
- package/packages/adapters-log-ringbuffer/src/index.ts +212 -0
- package/packages/adapters-log-ringbuffer/src/manifest.ts +30 -0
- package/packages/adapters-log-ringbuffer/tsconfig.build.json +15 -0
- package/packages/adapters-log-ringbuffer/tsconfig.json +9 -0
- package/packages/adapters-log-ringbuffer/tsup.config.ts +9 -0
- package/packages/adapters-log-ringbuffer/vitest.config.ts +14 -0
- package/packages/adapters-log-sqlite/README.md +396 -0
- package/packages/adapters-log-sqlite/eslint.config.js +27 -0
- package/packages/adapters-log-sqlite/package.json +49 -0
- package/packages/adapters-log-sqlite/src/__tests__/log-persistence.test.ts +718 -0
- package/packages/adapters-log-sqlite/src/index.ts +1068 -0
- package/packages/adapters-log-sqlite/src/manifest.ts +36 -0
- package/packages/adapters-log-sqlite/src/schema.sql +46 -0
- package/packages/adapters-log-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-log-sqlite/tsconfig.json +9 -0
- package/packages/adapters-log-sqlite/tsup.config.ts +9 -0
- package/packages/adapters-log-sqlite/vitest.config.ts +15 -0
- package/packages/adapters-mongodb/README.md +147 -0
- package/packages/adapters-mongodb/eslint.config.js +27 -0
- package/packages/adapters-mongodb/package.json +53 -0
- package/packages/adapters-mongodb/src/index.ts +428 -0
- package/packages/adapters-mongodb/src/manifest.ts +45 -0
- package/packages/adapters-mongodb/src/secure-document.ts +231 -0
- package/packages/adapters-mongodb/tsconfig.build.json +15 -0
- package/packages/adapters-mongodb/tsconfig.json +9 -0
- package/packages/adapters-mongodb/tsup.config.ts +8 -0
- package/packages/adapters-openai/README.md +151 -0
- package/packages/adapters-openai/embeddings.ts +37 -0
- package/packages/adapters-openai/eslint.config.js +26 -0
- package/packages/adapters-openai/index.ts +22 -0
- package/packages/adapters-openai/package.json +57 -0
- package/packages/adapters-openai/src/embeddings-manifest.ts +45 -0
- package/packages/adapters-openai/src/embeddings.ts +104 -0
- package/packages/adapters-openai/src/index.ts +13 -0
- package/packages/adapters-openai/src/llm.ts +304 -0
- package/packages/adapters-openai/src/manifest.ts +47 -0
- package/packages/adapters-openai/tsconfig.build.json +15 -0
- package/packages/adapters-openai/tsconfig.json +9 -0
- package/packages/adapters-openai/tsup.config.ts +8 -0
- package/packages/adapters-pino/README.md +152 -0
- package/packages/adapters-pino/eslint.config.js +27 -0
- package/packages/adapters-pino/package.json +49 -0
- package/packages/adapters-pino/src/index.test.ts +44 -0
- package/packages/adapters-pino/src/index.ts +322 -0
- package/packages/adapters-pino/src/log-ring-buffer.ts +142 -0
- package/packages/adapters-pino/src/manifest.ts +49 -0
- package/packages/adapters-pino/tsconfig.build.json +15 -0
- package/packages/adapters-pino/tsconfig.json +9 -0
- package/packages/adapters-pino/tsup.config.ts +9 -0
- package/packages/adapters-pino-http/README.md +141 -0
- package/packages/adapters-pino-http/eslint.config.js +27 -0
- package/packages/adapters-pino-http/package.json +46 -0
- package/packages/adapters-pino-http/src/index.ts +229 -0
- package/packages/adapters-pino-http/tsconfig.build.json +15 -0
- package/packages/adapters-pino-http/tsconfig.json +9 -0
- package/packages/adapters-pino-http/tsup.config.ts +9 -0
- package/packages/adapters-qdrant/README.md +166 -0
- package/packages/adapters-qdrant/eslint.config.js +27 -0
- package/packages/adapters-qdrant/package.json +49 -0
- package/packages/adapters-qdrant/src/index.ts +490 -0
- package/packages/adapters-qdrant/src/manifest.ts +54 -0
- package/packages/adapters-qdrant/src/retry.ts +204 -0
- package/packages/adapters-qdrant/tsconfig.build.json +15 -0
- package/packages/adapters-qdrant/tsconfig.json +9 -0
- package/packages/adapters-qdrant/tsup.config.ts +9 -0
- package/packages/adapters-redis/README.md +159 -0
- package/packages/adapters-redis/eslint.config.js +27 -0
- package/packages/adapters-redis/package.json +49 -0
- package/packages/adapters-redis/src/index.ts +164 -0
- package/packages/adapters-redis/src/manifest.ts +49 -0
- package/packages/adapters-redis/tsconfig.build.json +15 -0
- package/packages/adapters-redis/tsconfig.json +9 -0
- package/packages/adapters-redis/tsup.config.ts +9 -0
- package/packages/adapters-snapshot-localfs/README.md +10 -0
- package/packages/adapters-snapshot-localfs/eslint.config.js +2 -0
- package/packages/adapters-snapshot-localfs/package.json +46 -0
- package/packages/adapters-snapshot-localfs/src/index.test.ts +40 -0
- package/packages/adapters-snapshot-localfs/src/index.ts +292 -0
- package/packages/adapters-snapshot-localfs/src/manifest.ts +32 -0
- package/packages/adapters-snapshot-localfs/tsconfig.build.json +15 -0
- package/packages/adapters-snapshot-localfs/tsconfig.json +16 -0
- package/packages/adapters-snapshot-localfs/tsup.config.ts +11 -0
- package/packages/adapters-sqlite/README.md +163 -0
- package/packages/adapters-sqlite/eslint.config.js +27 -0
- package/packages/adapters-sqlite/package.json +54 -0
- package/packages/adapters-sqlite/src/index.test.ts +245 -0
- package/packages/adapters-sqlite/src/index.ts +382 -0
- package/packages/adapters-sqlite/src/manifest.ts +47 -0
- package/packages/adapters-sqlite/src/secure-sql.test.ts +290 -0
- package/packages/adapters-sqlite/src/secure-sql.ts +281 -0
- package/packages/adapters-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-sqlite/tsconfig.json +9 -0
- package/packages/adapters-sqlite/tsup.config.ts +8 -0
- package/packages/adapters-sqlite/vitest.config.ts +19 -0
- package/packages/adapters-transport/README.md +170 -0
- package/packages/adapters-transport/eslint.config.js +27 -0
- package/packages/adapters-transport/package.json +49 -0
- package/packages/adapters-transport/src/__tests__/unix-socket-server.test.ts +550 -0
- package/packages/adapters-transport/src/index.ts +101 -0
- package/packages/adapters-transport/src/ipc-transport.ts +228 -0
- package/packages/adapters-transport/src/transport.ts +224 -0
- package/packages/adapters-transport/src/types.ts +92 -0
- package/packages/adapters-transport/src/unix-socket-server.ts +193 -0
- package/packages/adapters-transport/src/unix-socket-transport.ts +280 -0
- package/packages/adapters-transport/tsconfig.build.json +15 -0
- package/packages/adapters-transport/tsconfig.json +9 -0
- package/packages/adapters-transport/tsup.config.ts +9 -0
- package/packages/adapters-vibeproxy/README.md +159 -0
- package/packages/adapters-vibeproxy/eslint.config.js +27 -0
- package/packages/adapters-vibeproxy/package.json +51 -0
- package/packages/adapters-vibeproxy/src/index.ts +13 -0
- package/packages/adapters-vibeproxy/src/llm.ts +437 -0
- package/packages/adapters-vibeproxy/src/manifest.ts +51 -0
- package/packages/adapters-vibeproxy/tsconfig.build.json +15 -0
- package/packages/adapters-vibeproxy/tsconfig.json +9 -0
- package/packages/adapters-vibeproxy/tsup.config.ts +8 -0
- package/packages/adapters-workspace-agent/package.json +46 -0
- package/packages/adapters-workspace-agent/src/__tests__/adapter.test.ts +212 -0
- package/packages/adapters-workspace-agent/src/index.ts +220 -0
- package/packages/adapters-workspace-agent/src/manifest.ts +36 -0
- package/packages/adapters-workspace-agent/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-agent/tsconfig.json +16 -0
- package/packages/adapters-workspace-agent/tsup.config.ts +11 -0
- package/packages/adapters-workspace-localfs/README.md +9 -0
- package/packages/adapters-workspace-localfs/eslint.config.js +2 -0
- package/packages/adapters-workspace-localfs/package.json +46 -0
- package/packages/adapters-workspace-localfs/src/index.test.ts +27 -0
- package/packages/adapters-workspace-localfs/src/index.ts +172 -0
- package/packages/adapters-workspace-localfs/src/manifest.ts +32 -0
- package/packages/adapters-workspace-localfs/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-localfs/tsconfig.json +16 -0
- package/packages/adapters-workspace-localfs/tsup.config.ts +11 -0
- package/packages/adapters-workspace-worktree/README.md +9 -0
- package/packages/adapters-workspace-worktree/eslint.config.js +2 -0
- package/packages/adapters-workspace-worktree/package.json +46 -0
- package/packages/adapters-workspace-worktree/src/index.test.ts +38 -0
- package/packages/adapters-workspace-worktree/src/index.ts +245 -0
- package/packages/adapters-workspace-worktree/src/manifest.ts +38 -0
- package/packages/adapters-workspace-worktree/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-worktree/tsconfig.json +16 -0
- package/packages/adapters-workspace-worktree/tsup.config.ts +11 -0
- package/pnpm-workspace.yaml +2800 -0
- package/prettierrc.json +1 -0
- package/scripts/devkit-sync.mjs +37 -0
- package/scripts/hooks/post-push +9 -0
- package/scripts/hooks/pre-commit +9 -0
- package/scripts/hooks/pre-push +9 -0
- package/test-integration.ts +242 -0
- package/test.txt +1 -0
- package/tsconfig.base.json +6 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +9 -0
- package/tsconfig.paths.json +26 -0
- package/tsconfig.tools.json +17 -0
- package/tsup.config.bin.ts +34 -0
- package/tsup.config.cli.ts +41 -0
- package/tsup.config.dual.ts +46 -0
- package/tsup.config.ts +36 -0
- package/tsup.external.json +103 -0
- package/vitest.config.ts +2 -0
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for LogSQLitePersistence
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
6
|
+
import { LogSQLitePersistence } from "../index";
|
|
7
|
+
import { createAdapter as createSQLiteDB } from "@kb-labs/adapters-sqlite";
|
|
8
|
+
import type { LogRecord, ISQLDatabase } from "@kb-labs/core-platform/adapters";
|
|
9
|
+
|
|
10
|
+
describe("LogSQLitePersistence", () => {
|
|
11
|
+
let db: ISQLDatabase;
|
|
12
|
+
let persistence: LogSQLitePersistence;
|
|
13
|
+
const testDbPath = ":memory:"; // Use in-memory database for tests
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
// Create in-memory SQLite database
|
|
17
|
+
db = createSQLiteDB({ filename: testDbPath });
|
|
18
|
+
|
|
19
|
+
// Create persistence adapter
|
|
20
|
+
persistence = new LogSQLitePersistence({
|
|
21
|
+
database: db,
|
|
22
|
+
tableName: "logs",
|
|
23
|
+
batchSize: 5, // Small batch for testing
|
|
24
|
+
flushInterval: 100, // Fast flush for testing
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await persistence.initialize();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(async () => {
|
|
31
|
+
await persistence.close();
|
|
32
|
+
await db.close();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("write", () => {
|
|
36
|
+
it("should write log to database", async () => {
|
|
37
|
+
const log: LogRecord = {
|
|
38
|
+
id: "test-1",
|
|
39
|
+
timestamp: Date.now(),
|
|
40
|
+
level: "info",
|
|
41
|
+
message: "Test log",
|
|
42
|
+
fields: {},
|
|
43
|
+
source: "test",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
await persistence.write(log);
|
|
47
|
+
|
|
48
|
+
// Wait for flush
|
|
49
|
+
await new Promise((resolve) => {
|
|
50
|
+
setTimeout(resolve, 150);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const result = await persistence.query({});
|
|
54
|
+
expect(result.logs).toHaveLength(1);
|
|
55
|
+
expect(result.logs[0]!.message).toBe("Test log");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should flush when batch size is reached", async () => {
|
|
59
|
+
// Write 5 logs (batch size)
|
|
60
|
+
for (let i = 0; i < 5; i++) {
|
|
61
|
+
|
|
62
|
+
await persistence.write({
|
|
63
|
+
id: `test-batch-${i}`,
|
|
64
|
+
timestamp: Date.now() + i,
|
|
65
|
+
level: "info",
|
|
66
|
+
message: `Log ${i}`,
|
|
67
|
+
fields: {},
|
|
68
|
+
source: "test",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Should flush immediately without waiting for interval
|
|
73
|
+
const result = await persistence.query({});
|
|
74
|
+
expect(result.logs).toHaveLength(5);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should preserve log fields", async () => {
|
|
78
|
+
const complexFields = {
|
|
79
|
+
user: { id: 123, name: "Alice" },
|
|
80
|
+
tags: ["api", "error"],
|
|
81
|
+
count: 42,
|
|
82
|
+
nested: { deep: { value: "test" } },
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
await persistence.write({
|
|
86
|
+
id: "test-2",
|
|
87
|
+
timestamp: Date.now(),
|
|
88
|
+
level: "error",
|
|
89
|
+
message: "Complex log",
|
|
90
|
+
fields: complexFields,
|
|
91
|
+
source: "test",
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await new Promise((resolve) => {
|
|
95
|
+
setTimeout(resolve, 150);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const result = await persistence.query({});
|
|
99
|
+
expect(result.logs[0]!.fields).toEqual(complexFields);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should handle empty fields", async () => {
|
|
103
|
+
await persistence.write({
|
|
104
|
+
id: "test-3",
|
|
105
|
+
timestamp: Date.now(),
|
|
106
|
+
level: "info",
|
|
107
|
+
message: "No fields",
|
|
108
|
+
fields: {},
|
|
109
|
+
source: "test",
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await new Promise((resolve) => {
|
|
113
|
+
setTimeout(resolve, 150);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const result = await persistence.query({});
|
|
117
|
+
expect(result.logs[0]!.fields).toEqual({});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should generate ID if not provided", async () => {
|
|
121
|
+
await persistence.write({
|
|
122
|
+
id: "test-4",
|
|
123
|
+
timestamp: Date.now(),
|
|
124
|
+
level: "info",
|
|
125
|
+
message: "No ID",
|
|
126
|
+
fields: {},
|
|
127
|
+
source: "test",
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await new Promise((resolve) => {
|
|
131
|
+
setTimeout(resolve, 150);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const result = await persistence.query({});
|
|
135
|
+
expect(result.logs[0]!.id).toBeDefined();
|
|
136
|
+
expect(typeof result.logs[0]!.id).toBe("string");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should preserve custom ID", async () => {
|
|
140
|
+
const customId = "custom-log-id-123";
|
|
141
|
+
|
|
142
|
+
await persistence.write({
|
|
143
|
+
id: customId,
|
|
144
|
+
timestamp: Date.now(),
|
|
145
|
+
level: "info",
|
|
146
|
+
message: "Custom ID",
|
|
147
|
+
fields: {},
|
|
148
|
+
source: "test",
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await new Promise((resolve) => {
|
|
152
|
+
setTimeout(resolve, 150);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const result = await persistence.query({});
|
|
156
|
+
expect(result.logs[0]!.id).toBe(customId);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("writeBatch", () => {
|
|
161
|
+
it("should write multiple logs in batch", async () => {
|
|
162
|
+
const logs: LogRecord[] = Array.from({ length: 10 }, (_, i) => ({
|
|
163
|
+
id: `test-batch-write-${i}`,
|
|
164
|
+
timestamp: Date.now() + i,
|
|
165
|
+
level: "info",
|
|
166
|
+
message: `Batch log ${i}`,
|
|
167
|
+
fields: {},
|
|
168
|
+
source: "test",
|
|
169
|
+
}));
|
|
170
|
+
|
|
171
|
+
await persistence.writeBatch(logs);
|
|
172
|
+
|
|
173
|
+
// Wait for flush
|
|
174
|
+
await new Promise((resolve) => {
|
|
175
|
+
setTimeout(resolve, 150);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const result = await persistence.query({});
|
|
179
|
+
expect(result.logs).toHaveLength(10);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should handle empty batch", async () => {
|
|
183
|
+
await persistence.writeBatch([]);
|
|
184
|
+
|
|
185
|
+
const result = await persistence.query({});
|
|
186
|
+
expect(result.logs).toHaveLength(0);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe("query", () => {
|
|
191
|
+
beforeEach(async () => {
|
|
192
|
+
// Add test logs
|
|
193
|
+
const logs: LogRecord[] = [
|
|
194
|
+
{
|
|
195
|
+
id: "test-query-1",
|
|
196
|
+
timestamp: 1000,
|
|
197
|
+
level: "debug",
|
|
198
|
+
message: "Debug log",
|
|
199
|
+
fields: {},
|
|
200
|
+
source: "test",
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: "test-query-2",
|
|
204
|
+
timestamp: 2000,
|
|
205
|
+
level: "info",
|
|
206
|
+
message: "Info log",
|
|
207
|
+
fields: {},
|
|
208
|
+
source: "test",
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: "test-query-3",
|
|
212
|
+
timestamp: 3000,
|
|
213
|
+
level: "warn",
|
|
214
|
+
message: "Warn log",
|
|
215
|
+
fields: {},
|
|
216
|
+
source: "api",
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: "test-query-4",
|
|
220
|
+
timestamp: 4000,
|
|
221
|
+
level: "error",
|
|
222
|
+
message: "Error log",
|
|
223
|
+
fields: {},
|
|
224
|
+
source: "api",
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: "test-query-5",
|
|
228
|
+
timestamp: 5000,
|
|
229
|
+
level: "fatal",
|
|
230
|
+
message: "Fatal log",
|
|
231
|
+
fields: {},
|
|
232
|
+
source: "test",
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
await persistence.writeBatch(logs);
|
|
237
|
+
await new Promise((resolve) => {
|
|
238
|
+
setTimeout(resolve, 150);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("should return all logs without filters", async () => {
|
|
243
|
+
const result = await persistence.query({});
|
|
244
|
+
expect(result.logs).toHaveLength(5);
|
|
245
|
+
expect(result.total).toBe(5);
|
|
246
|
+
expect(result.hasMore).toBe(false);
|
|
247
|
+
expect(result.logs[0]!.timestamp).toBe(5000); // Newest first (DESC)
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("should filter by level", async () => {
|
|
251
|
+
const result = await persistence.query({ level: "error" });
|
|
252
|
+
expect(result.logs).toHaveLength(1);
|
|
253
|
+
expect(result.logs[0]!.level).toBe("error");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("should filter by source", async () => {
|
|
257
|
+
const result = await persistence.query({ source: "api" });
|
|
258
|
+
expect(result.logs).toHaveLength(2);
|
|
259
|
+
expect(result.logs.every((l) => l.source === "api")).toBe(true);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("should filter by timestamp range", async () => {
|
|
263
|
+
const result = await persistence.query({ from: 2000, to: 4000 });
|
|
264
|
+
expect(result.logs).toHaveLength(3);
|
|
265
|
+
expect(result.logs[0]!.timestamp).toBe(4000);
|
|
266
|
+
expect(result.logs[2]!.timestamp).toBe(2000);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("should apply limit", async () => {
|
|
270
|
+
const result = await persistence.query({}, { limit: 2 });
|
|
271
|
+
expect(result.logs).toHaveLength(2);
|
|
272
|
+
expect(result.total).toBe(5);
|
|
273
|
+
expect(result.hasMore).toBe(true);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("should apply offset", async () => {
|
|
277
|
+
const result = await persistence.query({}, { limit: 2, offset: 2 });
|
|
278
|
+
expect(result.logs).toHaveLength(2);
|
|
279
|
+
expect(result.total).toBe(5);
|
|
280
|
+
expect(result.hasMore).toBe(true);
|
|
281
|
+
expect(result.logs[0]!.timestamp).toBe(3000); // 3rd log
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("should sort by timestamp ascending", async () => {
|
|
285
|
+
const result = await persistence.query(
|
|
286
|
+
{},
|
|
287
|
+
{ sortBy: "timestamp", sortOrder: "asc" },
|
|
288
|
+
);
|
|
289
|
+
expect(result.logs[0]!.timestamp).toBe(1000); // Oldest first
|
|
290
|
+
expect(result.logs[4]!.timestamp).toBe(5000); // Newest last
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("should combine multiple filters", async () => {
|
|
294
|
+
const result = await persistence.query(
|
|
295
|
+
{ level: "error", source: "api", from: 3500 },
|
|
296
|
+
{ limit: 10 },
|
|
297
|
+
);
|
|
298
|
+
expect(result.logs).toHaveLength(1);
|
|
299
|
+
expect(result.logs[0]!.level).toBe("error");
|
|
300
|
+
expect(result.logs[0]!.source).toBe("api");
|
|
301
|
+
expect(result.logs[0]!.timestamp).toBeGreaterThanOrEqual(3500);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("should handle pagination correctly", async () => {
|
|
305
|
+
// First page
|
|
306
|
+
const page1 = await persistence.query({}, { limit: 2, offset: 0 });
|
|
307
|
+
expect(page1.logs).toHaveLength(2);
|
|
308
|
+
expect(page1.hasMore).toBe(true);
|
|
309
|
+
|
|
310
|
+
// Second page
|
|
311
|
+
const page2 = await persistence.query({}, { limit: 2, offset: 2 });
|
|
312
|
+
expect(page2.logs).toHaveLength(2);
|
|
313
|
+
expect(page2.hasMore).toBe(true);
|
|
314
|
+
|
|
315
|
+
// Third page
|
|
316
|
+
const page3 = await persistence.query({}, { limit: 2, offset: 4 });
|
|
317
|
+
expect(page3.logs).toHaveLength(1);
|
|
318
|
+
expect(page3.hasMore).toBe(false);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
describe("getById", () => {
|
|
323
|
+
it("should retrieve log by ID", async () => {
|
|
324
|
+
const logId = "test-log-123";
|
|
325
|
+
|
|
326
|
+
await persistence.write({
|
|
327
|
+
id: logId,
|
|
328
|
+
timestamp: Date.now(),
|
|
329
|
+
level: "info",
|
|
330
|
+
message: "Test log",
|
|
331
|
+
fields: { foo: "bar" },
|
|
332
|
+
source: "test",
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
await new Promise((resolve) => {
|
|
336
|
+
setTimeout(resolve, 150);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const log = await persistence.getById(logId);
|
|
340
|
+
expect(log).toBeDefined();
|
|
341
|
+
expect(log?.id).toBe(logId);
|
|
342
|
+
expect(log?.message).toBe("Test log");
|
|
343
|
+
expect(log?.fields).toEqual({ foo: "bar" });
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("should return null for non-existent ID", async () => {
|
|
347
|
+
const log = await persistence.getById("non-existent-id");
|
|
348
|
+
expect(log).toBeNull();
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
describe("search", () => {
|
|
353
|
+
beforeEach(async () => {
|
|
354
|
+
const logs: LogRecord[] = [
|
|
355
|
+
{
|
|
356
|
+
id: "test-search-1",
|
|
357
|
+
timestamp: 1000,
|
|
358
|
+
level: "info",
|
|
359
|
+
message: "User authentication succeeded",
|
|
360
|
+
fields: {},
|
|
361
|
+
source: "auth",
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
id: "test-search-2",
|
|
365
|
+
timestamp: 2000,
|
|
366
|
+
level: "error",
|
|
367
|
+
message: "User authentication failed",
|
|
368
|
+
fields: {},
|
|
369
|
+
source: "auth",
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
id: "test-search-3",
|
|
373
|
+
timestamp: 3000,
|
|
374
|
+
level: "info",
|
|
375
|
+
message: "Database connection established",
|
|
376
|
+
fields: {},
|
|
377
|
+
source: "db",
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
id: "test-search-4",
|
|
381
|
+
timestamp: 4000,
|
|
382
|
+
level: "error",
|
|
383
|
+
message: "Database query timeout",
|
|
384
|
+
fields: {},
|
|
385
|
+
source: "db",
|
|
386
|
+
},
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
await persistence.writeBatch(logs);
|
|
390
|
+
await new Promise((resolve) => {
|
|
391
|
+
setTimeout(resolve, 150);
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("should search logs by text", async () => {
|
|
396
|
+
const result = await persistence.search("authentication");
|
|
397
|
+
expect(result.logs).toHaveLength(2);
|
|
398
|
+
expect(
|
|
399
|
+
result.logs.every((l) => l.message.includes("authentication")),
|
|
400
|
+
).toBe(true);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("should search with multiple terms", async () => {
|
|
404
|
+
const result = await persistence.search("database");
|
|
405
|
+
expect(result.logs).toHaveLength(2);
|
|
406
|
+
expect(
|
|
407
|
+
result.logs.every((l) => l.message.toLowerCase().includes("database")),
|
|
408
|
+
).toBe(true);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("should apply limit to search results", async () => {
|
|
412
|
+
const result = await persistence.search("User", { limit: 1 });
|
|
413
|
+
expect(result.logs).toHaveLength(1);
|
|
414
|
+
expect(result.total).toBe(2);
|
|
415
|
+
expect(result.hasMore).toBe(true);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it("should return empty results for no matches", async () => {
|
|
419
|
+
const result = await persistence.search("nonexistent");
|
|
420
|
+
expect(result.logs).toHaveLength(0);
|
|
421
|
+
expect(result.total).toBe(0);
|
|
422
|
+
expect(result.hasMore).toBe(false);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it("should handle pagination in search", async () => {
|
|
426
|
+
const page1 = await persistence.search("User", { limit: 1, offset: 0 });
|
|
427
|
+
const page2 = await persistence.search("User", { limit: 1, offset: 1 });
|
|
428
|
+
|
|
429
|
+
expect(page1.logs).toHaveLength(1);
|
|
430
|
+
expect(page2.logs).toHaveLength(1);
|
|
431
|
+
expect(page1.logs[0]!.id).not.toBe(page2.logs[0]!.id);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it("should fallback to phrase search for hyphenated plain text", async () => {
|
|
435
|
+
await persistence.write({
|
|
436
|
+
id: "test-search-hyphen",
|
|
437
|
+
timestamp: 5000,
|
|
438
|
+
level: "info",
|
|
439
|
+
message: "kb-labs-commit-plugin initialized",
|
|
440
|
+
fields: {},
|
|
441
|
+
source: "commit",
|
|
442
|
+
});
|
|
443
|
+
await new Promise((resolve) => {
|
|
444
|
+
setTimeout(resolve, 150);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const result = await persistence.search("kb-labs-commit-plugin");
|
|
448
|
+
expect(result.logs).toHaveLength(1);
|
|
449
|
+
expect(result.logs[0]!.id).toBe("test-search-hyphen");
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it("should auto-rebuild stale FTS index when count and rows diverge", async () => {
|
|
453
|
+
// Insert stale index row with non-existent content rowid.
|
|
454
|
+
await db.query(
|
|
455
|
+
"INSERT INTO logs_fts(rowid, message) VALUES (?, ?)",
|
|
456
|
+
[999999, "phase 3 stale marker"],
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
const result = await persistence.search("phase 3 stale marker");
|
|
460
|
+
expect(result.logs).toHaveLength(0);
|
|
461
|
+
expect(result.total).toBe(0);
|
|
462
|
+
expect(result.hasMore).toBe(false);
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
describe("deleteOlderThan", () => {
|
|
467
|
+
beforeEach(async () => {
|
|
468
|
+
const logs: LogRecord[] = [
|
|
469
|
+
{
|
|
470
|
+
id: "test-old-1",
|
|
471
|
+
timestamp: 1000,
|
|
472
|
+
level: "info",
|
|
473
|
+
message: "Old log 1",
|
|
474
|
+
fields: {},
|
|
475
|
+
source: "test",
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
id: "test-old-2",
|
|
479
|
+
timestamp: 2000,
|
|
480
|
+
level: "info",
|
|
481
|
+
message: "Old log 2",
|
|
482
|
+
fields: {},
|
|
483
|
+
source: "test",
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
id: "test-new-1",
|
|
487
|
+
timestamp: 5000,
|
|
488
|
+
level: "info",
|
|
489
|
+
message: "New log 1",
|
|
490
|
+
fields: {},
|
|
491
|
+
source: "test",
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
id: "test-new-2",
|
|
495
|
+
timestamp: 6000,
|
|
496
|
+
level: "info",
|
|
497
|
+
message: "New log 2",
|
|
498
|
+
fields: {},
|
|
499
|
+
source: "test",
|
|
500
|
+
},
|
|
501
|
+
];
|
|
502
|
+
|
|
503
|
+
await persistence.writeBatch(logs);
|
|
504
|
+
await new Promise((resolve) => {
|
|
505
|
+
setTimeout(resolve, 150);
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it("should delete logs older than timestamp", async () => {
|
|
510
|
+
const deleted = await persistence.deleteOlderThan(3000);
|
|
511
|
+
expect(deleted).toBe(2); // 2 old logs deleted
|
|
512
|
+
|
|
513
|
+
const result = await persistence.query({});
|
|
514
|
+
expect(result.logs).toHaveLength(2);
|
|
515
|
+
expect(result.logs.every((l) => l.timestamp >= 3000)).toBe(true);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it("should return 0 if no logs deleted", async () => {
|
|
519
|
+
const deleted = await persistence.deleteOlderThan(500);
|
|
520
|
+
expect(deleted).toBe(0);
|
|
521
|
+
|
|
522
|
+
const result = await persistence.query({});
|
|
523
|
+
expect(result.logs).toHaveLength(4); // All logs still there
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it("should delete all logs if timestamp is in future", async () => {
|
|
527
|
+
const deleted = await persistence.deleteOlderThan(Date.now() + 10000);
|
|
528
|
+
expect(deleted).toBe(4);
|
|
529
|
+
|
|
530
|
+
const result = await persistence.query({});
|
|
531
|
+
expect(result.logs).toHaveLength(0);
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
describe("getStats", () => {
|
|
536
|
+
it("should return stats for empty database", async () => {
|
|
537
|
+
const stats = await persistence.getStats();
|
|
538
|
+
expect(stats.totalLogs).toBe(0);
|
|
539
|
+
expect(stats.oldestTimestamp).toBe(0);
|
|
540
|
+
expect(stats.newestTimestamp).toBe(0);
|
|
541
|
+
expect(stats.sizeBytes).toBeGreaterThanOrEqual(0);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it("should return correct stats", async () => {
|
|
545
|
+
const logs: LogRecord[] = [
|
|
546
|
+
{
|
|
547
|
+
id: "test-stats-1",
|
|
548
|
+
timestamp: 1000,
|
|
549
|
+
level: "info",
|
|
550
|
+
message: "Log 1",
|
|
551
|
+
fields: {},
|
|
552
|
+
source: "test",
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
id: "test-stats-2",
|
|
556
|
+
timestamp: 2000,
|
|
557
|
+
level: "info",
|
|
558
|
+
message: "Log 2",
|
|
559
|
+
fields: {},
|
|
560
|
+
source: "test",
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
id: "test-stats-3",
|
|
564
|
+
timestamp: 3000,
|
|
565
|
+
level: "info",
|
|
566
|
+
message: "Log 3",
|
|
567
|
+
fields: {},
|
|
568
|
+
source: "test",
|
|
569
|
+
},
|
|
570
|
+
];
|
|
571
|
+
|
|
572
|
+
await persistence.writeBatch(logs);
|
|
573
|
+
await new Promise((resolve) => {
|
|
574
|
+
setTimeout(resolve, 150);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
const stats = await persistence.getStats();
|
|
578
|
+
expect(stats.totalLogs).toBe(3);
|
|
579
|
+
expect(stats.oldestTimestamp).toBe(1000);
|
|
580
|
+
expect(stats.newestTimestamp).toBe(3000);
|
|
581
|
+
expect(stats.sizeBytes).toBeGreaterThan(0);
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
describe("close", () => {
|
|
586
|
+
it("should flush pending logs on close", async () => {
|
|
587
|
+
// Write logs but don't wait for flush
|
|
588
|
+
await persistence.write({
|
|
589
|
+
id: "test-pending",
|
|
590
|
+
timestamp: Date.now(),
|
|
591
|
+
level: "info",
|
|
592
|
+
message: "Pending log",
|
|
593
|
+
fields: {},
|
|
594
|
+
source: "test",
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
// Close immediately (should flush)
|
|
598
|
+
await persistence.close();
|
|
599
|
+
|
|
600
|
+
// Query should show the log
|
|
601
|
+
const result = await persistence.query({});
|
|
602
|
+
expect(result.logs).toHaveLength(1);
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
describe("transaction support", () => {
|
|
607
|
+
it("should rollback on error", async () => {
|
|
608
|
+
const logs: LogRecord[] = [
|
|
609
|
+
{
|
|
610
|
+
id: "test-txn-1",
|
|
611
|
+
timestamp: 1000,
|
|
612
|
+
level: "info",
|
|
613
|
+
message: "Log 1",
|
|
614
|
+
fields: {},
|
|
615
|
+
source: "test",
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
id: "test-txn-2",
|
|
619
|
+
timestamp: 2000,
|
|
620
|
+
level: "info",
|
|
621
|
+
message: "Log 2",
|
|
622
|
+
fields: {},
|
|
623
|
+
source: "test",
|
|
624
|
+
},
|
|
625
|
+
];
|
|
626
|
+
|
|
627
|
+
await persistence.writeBatch(logs);
|
|
628
|
+
await new Promise((resolve) => {
|
|
629
|
+
setTimeout(resolve, 150);
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
// Verify logs were written
|
|
633
|
+
const result = await persistence.query({});
|
|
634
|
+
expect(result.logs).toHaveLength(2);
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
describe("edge cases", () => {
|
|
639
|
+
it("should handle very long messages", async () => {
|
|
640
|
+
const longMessage = "A".repeat(10000);
|
|
641
|
+
|
|
642
|
+
await persistence.write({
|
|
643
|
+
id: "test-long",
|
|
644
|
+
timestamp: Date.now(),
|
|
645
|
+
level: "info",
|
|
646
|
+
message: longMessage,
|
|
647
|
+
fields: {},
|
|
648
|
+
source: "test",
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
await new Promise((resolve) => {
|
|
652
|
+
setTimeout(resolve, 150);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
const result = await persistence.query({});
|
|
656
|
+
expect(result.logs[0]!.message).toBe(longMessage);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it("should handle special characters in message", async () => {
|
|
660
|
+
const specialMessage =
|
|
661
|
+
"Test with 'quotes' and \"double quotes\" and \n newlines \t tabs";
|
|
662
|
+
|
|
663
|
+
await persistence.write({
|
|
664
|
+
id: "test-special",
|
|
665
|
+
timestamp: Date.now(),
|
|
666
|
+
level: "info",
|
|
667
|
+
message: specialMessage,
|
|
668
|
+
fields: {},
|
|
669
|
+
source: "test",
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
await new Promise((resolve) => {
|
|
673
|
+
setTimeout(resolve, 150);
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
const result = await persistence.query({});
|
|
677
|
+
expect(result.logs[0]!.message).toBe(specialMessage);
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
it("should handle unicode characters", async () => {
|
|
681
|
+
const unicodeMessage = "测试 тест 🚀 emoji";
|
|
682
|
+
|
|
683
|
+
await persistence.write({
|
|
684
|
+
id: "test-unicode",
|
|
685
|
+
timestamp: Date.now(),
|
|
686
|
+
level: "info",
|
|
687
|
+
message: unicodeMessage,
|
|
688
|
+
fields: {},
|
|
689
|
+
source: "test",
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
await new Promise((resolve) => {
|
|
693
|
+
setTimeout(resolve, 150);
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
const result = await persistence.query({});
|
|
697
|
+
expect(result.logs[0]!.message).toBe(unicodeMessage);
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
it("should handle negative timestamps", async () => {
|
|
701
|
+
await persistence.write({
|
|
702
|
+
id: "test-negative",
|
|
703
|
+
timestamp: -1000,
|
|
704
|
+
level: "info",
|
|
705
|
+
message: "Negative timestamp",
|
|
706
|
+
fields: {},
|
|
707
|
+
source: "test",
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
await new Promise((resolve) => {
|
|
711
|
+
setTimeout(resolve, 150);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
const result = await persistence.query({});
|
|
715
|
+
expect(result.logs[0]!.timestamp).toBe(-1000);
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
});
|