@agentlensai/server 0.2.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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/dist/__tests__/agents-stats.test.d.ts +5 -0
  3. package/dist/__tests__/agents-stats.test.d.ts.map +1 -0
  4. package/dist/__tests__/agents-stats.test.js +134 -0
  5. package/dist/__tests__/agents-stats.test.js.map +1 -0
  6. package/dist/__tests__/api-keys.test.d.ts +5 -0
  7. package/dist/__tests__/api-keys.test.d.ts.map +1 -0
  8. package/dist/__tests__/api-keys.test.js +118 -0
  9. package/dist/__tests__/api-keys.test.js.map +1 -0
  10. package/dist/__tests__/auth-no-db.test.d.ts +2 -0
  11. package/dist/__tests__/auth-no-db.test.d.ts.map +1 -0
  12. package/dist/__tests__/auth-no-db.test.js +43 -0
  13. package/dist/__tests__/auth-no-db.test.js.map +1 -0
  14. package/dist/__tests__/auth.test.d.ts +5 -0
  15. package/dist/__tests__/auth.test.d.ts.map +1 -0
  16. package/dist/__tests__/auth.test.js +86 -0
  17. package/dist/__tests__/auth.test.js.map +1 -0
  18. package/dist/__tests__/config.test.d.ts +2 -0
  19. package/dist/__tests__/config.test.d.ts.map +1 -0
  20. package/dist/__tests__/config.test.js +37 -0
  21. package/dist/__tests__/config.test.js.map +1 -0
  22. package/dist/__tests__/events-ingest.test.d.ts +5 -0
  23. package/dist/__tests__/events-ingest.test.d.ts.map +1 -0
  24. package/dist/__tests__/events-ingest.test.js +248 -0
  25. package/dist/__tests__/events-ingest.test.js.map +1 -0
  26. package/dist/__tests__/events-query.test.d.ts +5 -0
  27. package/dist/__tests__/events-query.test.d.ts.map +1 -0
  28. package/dist/__tests__/events-query.test.js +205 -0
  29. package/dist/__tests__/events-query.test.js.map +1 -0
  30. package/dist/__tests__/health.test.d.ts +5 -0
  31. package/dist/__tests__/health.test.d.ts.map +1 -0
  32. package/dist/__tests__/health.test.js +40 -0
  33. package/dist/__tests__/health.test.js.map +1 -0
  34. package/dist/__tests__/sessions.test.d.ts +5 -0
  35. package/dist/__tests__/sessions.test.d.ts.map +1 -0
  36. package/dist/__tests__/sessions.test.js +176 -0
  37. package/dist/__tests__/sessions.test.js.map +1 -0
  38. package/dist/__tests__/test-helpers.d.ts +24 -0
  39. package/dist/__tests__/test-helpers.d.ts.map +1 -0
  40. package/dist/__tests__/test-helpers.js +45 -0
  41. package/dist/__tests__/test-helpers.js.map +1 -0
  42. package/dist/config.d.ts +20 -0
  43. package/dist/config.d.ts.map +1 -0
  44. package/dist/config.js +20 -0
  45. package/dist/config.js.map +1 -0
  46. package/dist/db/__tests__/init.test.d.ts +2 -0
  47. package/dist/db/__tests__/init.test.d.ts.map +1 -0
  48. package/dist/db/__tests__/init.test.js +73 -0
  49. package/dist/db/__tests__/init.test.js.map +1 -0
  50. package/dist/db/__tests__/sqlite-store-read.test.d.ts +2 -0
  51. package/dist/db/__tests__/sqlite-store-read.test.d.ts.map +1 -0
  52. package/dist/db/__tests__/sqlite-store-read.test.js +749 -0
  53. package/dist/db/__tests__/sqlite-store-read.test.js.map +1 -0
  54. package/dist/db/__tests__/sqlite-store-write.test.d.ts +2 -0
  55. package/dist/db/__tests__/sqlite-store-write.test.d.ts.map +1 -0
  56. package/dist/db/__tests__/sqlite-store-write.test.js +418 -0
  57. package/dist/db/__tests__/sqlite-store-write.test.js.map +1 -0
  58. package/dist/db/errors.d.ts +16 -0
  59. package/dist/db/errors.d.ts.map +1 -0
  60. package/dist/db/errors.js +22 -0
  61. package/dist/db/errors.js.map +1 -0
  62. package/dist/db/index.d.ts +33 -0
  63. package/dist/db/index.d.ts.map +1 -0
  64. package/dist/db/index.js +44 -0
  65. package/dist/db/index.js.map +1 -0
  66. package/dist/db/migrate.d.ts +26 -0
  67. package/dist/db/migrate.d.ts.map +1 -0
  68. package/dist/db/migrate.js +128 -0
  69. package/dist/db/migrate.js.map +1 -0
  70. package/dist/db/schema.sqlite.d.ts +1009 -0
  71. package/dist/db/schema.sqlite.d.ts.map +1 -0
  72. package/dist/db/schema.sqlite.js +96 -0
  73. package/dist/db/schema.sqlite.js.map +1 -0
  74. package/dist/db/sqlite-store.d.ts +68 -0
  75. package/dist/db/sqlite-store.d.ts.map +1 -0
  76. package/dist/db/sqlite-store.js +753 -0
  77. package/dist/db/sqlite-store.js.map +1 -0
  78. package/dist/index.d.ts +45 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +182 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/lib/__tests__/retention.test.d.ts +2 -0
  83. package/dist/lib/__tests__/retention.test.d.ts.map +1 -0
  84. package/dist/lib/__tests__/retention.test.js +238 -0
  85. package/dist/lib/__tests__/retention.test.js.map +1 -0
  86. package/dist/lib/retention.d.ts +31 -0
  87. package/dist/lib/retention.d.ts.map +1 -0
  88. package/dist/lib/retention.js +47 -0
  89. package/dist/lib/retention.js.map +1 -0
  90. package/dist/middleware/auth.d.ts +37 -0
  91. package/dist/middleware/auth.d.ts.map +1 -0
  92. package/dist/middleware/auth.js +78 -0
  93. package/dist/middleware/auth.js.map +1 -0
  94. package/dist/routes/agents.d.ts +13 -0
  95. package/dist/routes/agents.d.ts.map +1 -0
  96. package/dist/routes/agents.js +34 -0
  97. package/dist/routes/agents.js.map +1 -0
  98. package/dist/routes/api-keys.d.ts +14 -0
  99. package/dist/routes/api-keys.d.ts.map +1 -0
  100. package/dist/routes/api-keys.js +81 -0
  101. package/dist/routes/api-keys.js.map +1 -0
  102. package/dist/routes/config.d.ts +39 -0
  103. package/dist/routes/config.d.ts.map +1 -0
  104. package/dist/routes/config.js +97 -0
  105. package/dist/routes/config.js.map +1 -0
  106. package/dist/routes/events.d.ts +14 -0
  107. package/dist/routes/events.d.ts.map +1 -0
  108. package/dist/routes/events.js +164 -0
  109. package/dist/routes/events.js.map +1 -0
  110. package/dist/routes/sessions.d.ts +14 -0
  111. package/dist/routes/sessions.d.ts.map +1 -0
  112. package/dist/routes/sessions.js +72 -0
  113. package/dist/routes/sessions.js.map +1 -0
  114. package/dist/routes/stats.d.ts +12 -0
  115. package/dist/routes/stats.d.ts.map +1 -0
  116. package/dist/routes/stats.js +16 -0
  117. package/dist/routes/stats.js.map +1 -0
  118. package/package.json +61 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amit Paz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for Story 4.7: Agent and Stats Endpoints
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=agents-stats.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents-stats.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/agents-stats.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Tests for Story 4.7: Agent and Stats Endpoints
3
+ */
4
+ import { describe, it, expect, beforeEach } from 'vitest';
5
+ import { createTestApp, authHeaders } from './test-helpers.js';
6
+ async function ingestEvents(app, apiKey, events) {
7
+ return app.request('/api/events', {
8
+ method: 'POST',
9
+ headers: authHeaders(apiKey),
10
+ body: JSON.stringify({ events }),
11
+ });
12
+ }
13
+ describe('Agent Endpoints (Story 4.7)', () => {
14
+ let app;
15
+ let apiKey;
16
+ beforeEach(async () => {
17
+ const ctx = createTestApp();
18
+ app = ctx.app;
19
+ apiKey = ctx.apiKey;
20
+ await ingestEvents(app, apiKey, [
21
+ {
22
+ sessionId: 'sess_001',
23
+ agentId: 'agent_001',
24
+ eventType: 'session_started',
25
+ payload: { agentName: 'Agent One', tags: [] },
26
+ },
27
+ ]);
28
+ await ingestEvents(app, apiKey, [
29
+ {
30
+ sessionId: 'sess_002',
31
+ agentId: 'agent_002',
32
+ eventType: 'session_started',
33
+ payload: { agentName: 'Agent Two', tags: [] },
34
+ },
35
+ ]);
36
+ });
37
+ describe('GET /api/agents', () => {
38
+ it('lists all agents', async () => {
39
+ const res = await app.request('/api/agents', {
40
+ headers: authHeaders(apiKey),
41
+ });
42
+ expect(res.status).toBe(200);
43
+ const body = await res.json();
44
+ expect(body.agents).toBeInstanceOf(Array);
45
+ expect(body.agents.length).toBe(2);
46
+ const names = body.agents.map((a) => a.name);
47
+ expect(names).toContain('Agent One');
48
+ expect(names).toContain('Agent Two');
49
+ });
50
+ it('includes agent metadata', async () => {
51
+ const res = await app.request('/api/agents', {
52
+ headers: authHeaders(apiKey),
53
+ });
54
+ const body = await res.json();
55
+ const agent = body.agents.find((a) => a.id === 'agent_001');
56
+ expect(agent.id).toBe('agent_001');
57
+ expect(agent.name).toBe('Agent One');
58
+ expect(agent.firstSeenAt).toBeTruthy();
59
+ expect(agent.lastSeenAt).toBeTruthy();
60
+ expect(agent.sessionCount).toBe(1);
61
+ });
62
+ });
63
+ describe('GET /api/agents/:id', () => {
64
+ it('returns a single agent', async () => {
65
+ const res = await app.request('/api/agents/agent_001', {
66
+ headers: authHeaders(apiKey),
67
+ });
68
+ expect(res.status).toBe(200);
69
+ const body = await res.json();
70
+ expect(body.id).toBe('agent_001');
71
+ expect(body.name).toBe('Agent One');
72
+ });
73
+ it('returns 404 for non-existent agent', async () => {
74
+ const res = await app.request('/api/agents/nonexistent', {
75
+ headers: authHeaders(apiKey),
76
+ });
77
+ expect(res.status).toBe(404);
78
+ const body = await res.json();
79
+ expect(body.error).toBe('Agent not found');
80
+ });
81
+ });
82
+ });
83
+ describe('Stats Endpoint (Story 4.7)', () => {
84
+ it('returns storage statistics for empty database', async () => {
85
+ const { app, apiKey } = createTestApp();
86
+ const res = await app.request('/api/stats', {
87
+ headers: authHeaders(apiKey),
88
+ });
89
+ expect(res.status).toBe(200);
90
+ const body = await res.json();
91
+ expect(body.totalEvents).toBe(0);
92
+ expect(body.totalSessions).toBe(0);
93
+ expect(body.totalAgents).toBe(0);
94
+ });
95
+ it('returns accurate stats after ingestion', async () => {
96
+ const { app, apiKey } = createTestApp();
97
+ await ingestEvents(app, apiKey, [
98
+ {
99
+ sessionId: 'sess_001',
100
+ agentId: 'agent_001',
101
+ eventType: 'session_started',
102
+ timestamp: '2026-01-01T10:00:00Z',
103
+ payload: { tags: [] },
104
+ },
105
+ {
106
+ sessionId: 'sess_001',
107
+ agentId: 'agent_001',
108
+ eventType: 'tool_call',
109
+ timestamp: '2026-01-01T10:01:00Z',
110
+ payload: { toolName: 'search', arguments: {}, callId: 'c1' },
111
+ },
112
+ ]);
113
+ await ingestEvents(app, apiKey, [
114
+ {
115
+ sessionId: 'sess_002',
116
+ agentId: 'agent_002',
117
+ eventType: 'session_started',
118
+ timestamp: '2026-01-02T10:00:00Z',
119
+ payload: { tags: [] },
120
+ },
121
+ ]);
122
+ const res = await app.request('/api/stats', {
123
+ headers: authHeaders(apiKey),
124
+ });
125
+ const body = await res.json();
126
+ expect(body.totalEvents).toBe(3);
127
+ expect(body.totalSessions).toBe(2);
128
+ expect(body.totalAgents).toBe(2);
129
+ expect(body.oldestEvent).toBeTruthy();
130
+ expect(body.newestEvent).toBeTruthy();
131
+ expect(typeof body.storageSizeBytes).toBe('number');
132
+ });
133
+ });
134
+ //# sourceMappingURL=agents-stats.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents-stats.test.js","sourceRoot":"","sources":["../../src/__tests__/agents-stats.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for Story 4.3: API Key Management Endpoints
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=api-keys.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-keys.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/api-keys.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Tests for Story 4.3: API Key Management Endpoints
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { createTestApp, authHeaders } from './test-helpers.js';
6
+ describe('API Key Management (Story 4.3)', () => {
7
+ describe('POST /api/keys', () => {
8
+ it('creates a new API key and returns the raw key once', async () => {
9
+ const { app, apiKey } = createTestApp();
10
+ const res = await app.request('/api/keys', {
11
+ method: 'POST',
12
+ headers: authHeaders(apiKey),
13
+ body: JSON.stringify({ name: 'My New Key', scopes: ['read', 'write'] }),
14
+ });
15
+ expect(res.status).toBe(201);
16
+ const body = await res.json();
17
+ expect(body.key).toMatch(/^als_[a-f0-9]{64}$/);
18
+ expect(body.name).toBe('My New Key');
19
+ expect(body.scopes).toEqual(['read', 'write']);
20
+ expect(body.id).toBeTruthy();
21
+ expect(body.createdAt).toBeTruthy();
22
+ });
23
+ it('creates a key with defaults when no name/scopes provided', async () => {
24
+ const { app, apiKey } = createTestApp();
25
+ const res = await app.request('/api/keys', {
26
+ method: 'POST',
27
+ headers: authHeaders(apiKey),
28
+ body: JSON.stringify({}),
29
+ });
30
+ expect(res.status).toBe(201);
31
+ const body = await res.json();
32
+ expect(body.name).toBe('Unnamed Key');
33
+ expect(body.scopes).toEqual(['*']);
34
+ });
35
+ });
36
+ describe('GET /api/keys', () => {
37
+ it('lists all keys without raw key in response', async () => {
38
+ const { app, apiKey } = createTestApp();
39
+ // Create an additional key
40
+ await app.request('/api/keys', {
41
+ method: 'POST',
42
+ headers: authHeaders(apiKey),
43
+ body: JSON.stringify({ name: 'Extra Key' }),
44
+ });
45
+ const res = await app.request('/api/keys', {
46
+ headers: authHeaders(apiKey),
47
+ });
48
+ expect(res.status).toBe(200);
49
+ const body = await res.json();
50
+ expect(body.keys).toBeInstanceOf(Array);
51
+ // At least the pre-seeded key + the one we just created
52
+ expect(body.keys.length).toBeGreaterThanOrEqual(2);
53
+ // No raw key in response
54
+ for (const key of body.keys) {
55
+ expect(key).not.toHaveProperty('key');
56
+ expect(key).not.toHaveProperty('keyHash');
57
+ expect(key.id).toBeTruthy();
58
+ expect(key.name).toBeTruthy();
59
+ }
60
+ });
61
+ });
62
+ describe('DELETE /api/keys/:id', () => {
63
+ it('revokes (soft deletes) an API key', async () => {
64
+ const { app, apiKey } = createTestApp();
65
+ // Create a key to revoke
66
+ const createRes = await app.request('/api/keys', {
67
+ method: 'POST',
68
+ headers: authHeaders(apiKey),
69
+ body: JSON.stringify({ name: 'To Revoke' }),
70
+ });
71
+ const { id: newKeyId } = await createRes.json();
72
+ // Revoke it
73
+ const delRes = await app.request(`/api/keys/${newKeyId}`, {
74
+ method: 'DELETE',
75
+ headers: authHeaders(apiKey),
76
+ });
77
+ expect(delRes.status).toBe(200);
78
+ const body = await delRes.json();
79
+ expect(body.revoked).toBe(true);
80
+ // Verify it shows as revoked in the list
81
+ const listRes = await app.request('/api/keys', {
82
+ headers: authHeaders(apiKey),
83
+ });
84
+ const listBody = await listRes.json();
85
+ const revokedKey = listBody.keys.find((k) => k.id === newKeyId);
86
+ expect(revokedKey?.revokedAt).toBeTruthy();
87
+ });
88
+ it('returns 404 for non-existent key', async () => {
89
+ const { app, apiKey } = createTestApp();
90
+ const res = await app.request('/api/keys/nonexistent-id', {
91
+ method: 'DELETE',
92
+ headers: authHeaders(apiKey),
93
+ });
94
+ expect(res.status).toBe(404);
95
+ });
96
+ it('returns 409 for already revoked key', async () => {
97
+ const { app, apiKey } = createTestApp();
98
+ // Create and revoke
99
+ const createRes = await app.request('/api/keys', {
100
+ method: 'POST',
101
+ headers: authHeaders(apiKey),
102
+ body: JSON.stringify({ name: 'Double Revoke' }),
103
+ });
104
+ const { id: keyId } = await createRes.json();
105
+ await app.request(`/api/keys/${keyId}`, {
106
+ method: 'DELETE',
107
+ headers: authHeaders(apiKey),
108
+ });
109
+ // Try to revoke again
110
+ const res = await app.request(`/api/keys/${keyId}`, {
111
+ method: 'DELETE',
112
+ headers: authHeaders(apiKey),
113
+ });
114
+ expect(res.status).toBe(409);
115
+ });
116
+ });
117
+ });
118
+ //# sourceMappingURL=api-keys.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-keys.test.js","sourceRoot":"","sources":["../../src/__tests__/api-keys.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth-no-db.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-no-db.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/auth-no-db.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Tests for createApp() without db when auth is enabled (Issue 4)
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { createApp } from '../index.js';
6
+ import { createTestDb } from '../db/index.js';
7
+ import { runMigrations } from '../db/migrate.js';
8
+ import { SqliteEventStore } from '../db/sqlite-store.js';
9
+ describe('createApp() auth without db (Issue 4)', () => {
10
+ it('throws when no db provided and auth is not disabled', () => {
11
+ const db = createTestDb();
12
+ runMigrations(db);
13
+ const store = new SqliteEventStore(db);
14
+ expect(() => {
15
+ createApp(store, { authDisabled: false });
16
+ }).toThrow('createApp() requires a `db` option when auth is enabled');
17
+ });
18
+ it('throws when no db provided and authDisabled is undefined (defaults to false)', () => {
19
+ const db = createTestDb();
20
+ runMigrations(db);
21
+ const store = new SqliteEventStore(db);
22
+ expect(() => {
23
+ createApp(store);
24
+ }).toThrow('createApp() requires a `db` option when auth is enabled');
25
+ });
26
+ it('does not throw when db is not provided but auth is disabled', () => {
27
+ const db = createTestDb();
28
+ runMigrations(db);
29
+ const store = new SqliteEventStore(db);
30
+ expect(() => {
31
+ createApp(store, { authDisabled: true });
32
+ }).not.toThrow();
33
+ });
34
+ it('does not throw when db is provided and auth is enabled', () => {
35
+ const db = createTestDb();
36
+ runMigrations(db);
37
+ const store = new SqliteEventStore(db);
38
+ expect(() => {
39
+ createApp(store, { authDisabled: false, db });
40
+ }).not.toThrow();
41
+ });
42
+ });
43
+ //# sourceMappingURL=auth-no-db.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-no-db.test.js","sourceRoot":"","sources":["../../src/__tests__/auth-no-db.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,QAAQ,CAAC,uCAAuC,EAAE,GAAG,EAAE;IACrD,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC;QAC1B,aAAa,CAAC,EAAE,CAAC,CAAC;QAClB,MAAM,KAAK,GAAG,IAAI,gBAAgB,CAAC,EAAE,CAAC,CAAC;QAEvC,MAAM,CAAC,GAAG,EAAE;YACV,SAAS,CAAC,KAAK,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC,OAAO,CAAC,yDAAyD,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8EAA8E,EAAE,GAAG,EAAE;QACtF,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC;QAC1B,aAAa,CAAC,EAAE,CAAC,CAAC;QAClB,MAAM,KAAK,GAAG,IAAI,gBAAgB,CAAC,EAAE,CAAC,CAAC;QAEvC,MAAM,CAAC,GAAG,EAAE;YACV,SAAS,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC,CAAC,CAAC,OAAO,CAAC,yDAAyD,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC;QAC1B,aAAa,CAAC,EAAE,CAAC,CAAC;QAClB,MAAM,KAAK,GAAG,IAAI,gBAAgB,CAAC,EAAE,CAAC,CAAC;QAEvC,MAAM,CAAC,GAAG,EAAE;YACV,SAAS,CAAC,KAAK,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC;QAC1B,aAAa,CAAC,EAAE,CAAC,CAAC;QAClB,MAAM,KAAK,GAAG,IAAI,gBAAgB,CAAC,EAAE,CAAC,CAAC;QAEvC,MAAM,CAAC,GAAG,EAAE;YACV,SAAS,CAAC,KAAK,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for Story 4.2: API Key Auth Middleware
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=auth.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/auth.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Tests for Story 4.2: API Key Auth Middleware
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { createTestApp, authHeaders } from './test-helpers.js';
6
+ import { hashApiKey } from '../middleware/auth.js';
7
+ import { apiKeys } from '../db/schema.sqlite.js';
8
+ import { eq } from 'drizzle-orm';
9
+ describe('API Key Auth Middleware (Story 4.2)', () => {
10
+ it('returns 401 when no Authorization header is provided', async () => {
11
+ const { app } = createTestApp();
12
+ const res = await app.request('/api/events');
13
+ expect(res.status).toBe(401);
14
+ const body = await res.json();
15
+ expect(body.error).toContain('Missing Authorization');
16
+ });
17
+ it('returns 401 for invalid header format', async () => {
18
+ const { app } = createTestApp();
19
+ const res = await app.request('/api/events', {
20
+ headers: { Authorization: 'Basic abc123' },
21
+ });
22
+ expect(res.status).toBe(401);
23
+ const body = await res.json();
24
+ expect(body.error).toContain('Invalid Authorization');
25
+ });
26
+ it('returns 401 for non-als_ prefixed key', async () => {
27
+ const { app } = createTestApp();
28
+ const res = await app.request('/api/events', {
29
+ headers: { Authorization: 'Bearer sk_badprefix123' },
30
+ });
31
+ expect(res.status).toBe(401);
32
+ });
33
+ it('returns 401 for unknown API key', async () => {
34
+ const { app } = createTestApp();
35
+ const res = await app.request('/api/events', {
36
+ headers: { Authorization: 'Bearer als_unknownkey1234567890abcdef1234567890abcdef1234567890abcdef12' },
37
+ });
38
+ expect(res.status).toBe(401);
39
+ const body = await res.json();
40
+ expect(body.error).toContain('Invalid or revoked');
41
+ });
42
+ it('returns 401 for revoked API key', async () => {
43
+ const { app, db } = createTestApp();
44
+ // Revoke the test key
45
+ db.update(apiKeys)
46
+ .set({ revokedAt: Math.floor(Date.now() / 1000) })
47
+ .where(eq(apiKeys.id, 'test-key-id'))
48
+ .run();
49
+ const res = await app.request('/api/events', {
50
+ headers: { Authorization: 'Bearer als_testkey123456789abcdef0123456789abcdef0123456789abcdef012345' },
51
+ });
52
+ expect(res.status).toBe(401);
53
+ });
54
+ it('allows requests with a valid API key', async () => {
55
+ const { app, apiKey } = createTestApp();
56
+ const res = await app.request('/api/events', {
57
+ headers: authHeaders(apiKey),
58
+ });
59
+ // Should be 200 (empty events list), not 401
60
+ expect(res.status).toBe(200);
61
+ });
62
+ it('skips auth when AUTH_DISABLED=true', async () => {
63
+ const { app } = createTestApp({ authDisabled: true });
64
+ // No auth header
65
+ const res = await app.request('/api/events');
66
+ expect(res.status).toBe(200);
67
+ });
68
+ it('updates lastUsedAt on successful auth', async () => {
69
+ const { app, db, apiKey } = createTestApp();
70
+ // Check lastUsedAt before
71
+ const before = db.select().from(apiKeys).where(eq(apiKeys.id, 'test-key-id')).get();
72
+ expect(before?.lastUsedAt).toBeNull();
73
+ await app.request('/api/events', {
74
+ headers: authHeaders(apiKey),
75
+ });
76
+ const after = db.select().from(apiKeys).where(eq(apiKeys.id, 'test-key-id')).get();
77
+ expect(after?.lastUsedAt).not.toBeNull();
78
+ });
79
+ it('hashApiKey produces consistent SHA-256 hex', () => {
80
+ const hash1 = hashApiKey('als_test');
81
+ const hash2 = hashApiKey('als_test');
82
+ expect(hash1).toBe(hash2);
83
+ expect(hash1).toHaveLength(64); // SHA-256 hex
84
+ });
85
+ });
86
+ //# sourceMappingURL=auth.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../../src/__tests__/auth.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Tests for server configuration (config.ts)
3
+ */
4
+ import { describe, it, expect, afterEach } from 'vitest';
5
+ import { getConfig } from '../config.js';
6
+ describe('getConfig()', () => {
7
+ afterEach(() => {
8
+ delete process.env['RETENTION_DAYS'];
9
+ delete process.env['PORT'];
10
+ });
11
+ it('defaults retentionDays to 90 when RETENTION_DAYS is not set', () => {
12
+ delete process.env['RETENTION_DAYS'];
13
+ const config = getConfig();
14
+ expect(config.retentionDays).toBe(90);
15
+ });
16
+ it('parses RETENTION_DAYS=0 as 0 (keep forever), not 90', () => {
17
+ process.env['RETENTION_DAYS'] = '0';
18
+ const config = getConfig();
19
+ expect(config.retentionDays).toBe(0);
20
+ });
21
+ it('parses RETENTION_DAYS=30 correctly', () => {
22
+ process.env['RETENTION_DAYS'] = '30';
23
+ const config = getConfig();
24
+ expect(config.retentionDays).toBe(30);
25
+ });
26
+ it('falls back to 90 for non-numeric RETENTION_DAYS', () => {
27
+ process.env['RETENTION_DAYS'] = 'abc';
28
+ const config = getConfig();
29
+ expect(config.retentionDays).toBe(90);
30
+ });
31
+ it('falls back to 90 for empty RETENTION_DAYS', () => {
32
+ process.env['RETENTION_DAYS'] = '';
33
+ const config = getConfig();
34
+ expect(config.retentionDays).toBe(90);
35
+ });
36
+ });
37
+ //# sourceMappingURL=config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QACrC,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QACrC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,GAAG,GAAG,CAAC;QACpC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC;QACrC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,GAAG,KAAK,CAAC;QACtC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,GAAG,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for Story 4.4: Event Ingestion Endpoint
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=events-ingest.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events-ingest.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/events-ingest.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}