@prmichaelsen/agentbase-core 0.1.0 → 0.1.1

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 (54) hide show
  1. package/README.md +234 -0
  2. package/dist/lib/auth/guards.test.d.ts +2 -0
  3. package/dist/lib/auth/guards.test.d.ts.map +1 -0
  4. package/dist/lib/auth/guards.test.js +105 -0
  5. package/dist/lib/auth/guards.test.js.map +1 -0
  6. package/dist/lib/auth/helpers.test.d.ts +2 -0
  7. package/dist/lib/auth/helpers.test.d.ts.map +1 -0
  8. package/dist/lib/auth/helpers.test.js +43 -0
  9. package/dist/lib/auth/helpers.test.js.map +1 -0
  10. package/dist/lib/auth/session.test.d.ts +2 -0
  11. package/dist/lib/auth/session.test.d.ts.map +1 -0
  12. package/dist/lib/auth/session.test.js +114 -0
  13. package/dist/lib/auth/session.test.js.map +1 -0
  14. package/dist/lib/firebase-admin.test.d.ts +2 -0
  15. package/dist/lib/firebase-admin.test.d.ts.map +1 -0
  16. package/dist/lib/firebase-admin.test.js +36 -0
  17. package/dist/lib/firebase-admin.test.js.map +1 -0
  18. package/dist/lib/firebase-client.test.d.ts +2 -0
  19. package/dist/lib/firebase-client.test.d.ts.map +1 -0
  20. package/dist/lib/firebase-client.test.js +167 -0
  21. package/dist/lib/firebase-client.test.js.map +1 -0
  22. package/dist/lib/format-time.test.d.ts +2 -0
  23. package/dist/lib/format-time.test.d.ts.map +1 -0
  24. package/dist/lib/format-time.test.js +41 -0
  25. package/dist/lib/format-time.test.js.map +1 -0
  26. package/dist/lib/linkify.test.d.ts +2 -0
  27. package/dist/lib/linkify.test.d.ts.map +1 -0
  28. package/dist/lib/linkify.test.js +40 -0
  29. package/dist/lib/linkify.test.js.map +1 -0
  30. package/dist/lib/logger.test.d.ts +2 -0
  31. package/dist/lib/logger.test.d.ts.map +1 -0
  32. package/dist/lib/logger.test.js +167 -0
  33. package/dist/lib/logger.test.js.map +1 -0
  34. package/dist/lib/rate-limiter.test.d.ts +2 -0
  35. package/dist/lib/rate-limiter.test.d.ts.map +1 -0
  36. package/dist/lib/rate-limiter.test.js +70 -0
  37. package/dist/lib/rate-limiter.test.js.map +1 -0
  38. package/dist/lib/uuid.test.d.ts +2 -0
  39. package/dist/lib/uuid.test.d.ts.map +1 -0
  40. package/dist/lib/uuid.test.js +22 -0
  41. package/dist/lib/uuid.test.js.map +1 -0
  42. package/dist/services/base.service.test.d.ts +2 -0
  43. package/dist/services/base.service.test.d.ts.map +1 -0
  44. package/dist/services/base.service.test.js +62 -0
  45. package/dist/services/base.service.test.js.map +1 -0
  46. package/dist/services/confirmation-token.service.test.d.ts +2 -0
  47. package/dist/services/confirmation-token.service.test.d.ts.map +1 -0
  48. package/dist/services/confirmation-token.service.test.js +65 -0
  49. package/dist/services/confirmation-token.service.test.js.map +1 -0
  50. package/dist/smoke.test.d.ts +2 -0
  51. package/dist/smoke.test.d.ts.map +1 -0
  52. package/dist/smoke.test.js +7 -0
  53. package/dist/smoke.test.js.map +1 -0
  54. package/package.json +8 -3
@@ -0,0 +1,167 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ const mockGetApps = vi.fn(() => []);
3
+ const mockGetApp = vi.fn();
4
+ const mockInitializeApp = vi.fn(() => ({ name: 'test-app' }));
5
+ const mockGetAuth = vi.fn(() => mockAuthInstance);
6
+ const mockSignInWithEmailAndPassword = vi.fn();
7
+ const mockCreateUserWithEmailAndPassword = vi.fn();
8
+ const mockFirebaseSignInAnonymously = vi.fn();
9
+ const mockLinkWithCredential = vi.fn();
10
+ const mockLinkWithPopup = vi.fn();
11
+ const mockSendPasswordResetEmail = vi.fn();
12
+ const mockSignOut = vi.fn();
13
+ const mockOnAuthStateChanged = vi.fn(() => vi.fn()); // returns unsubscribe
14
+ const mockAuthInstance = {
15
+ currentUser: null,
16
+ };
17
+ vi.mock('firebase/app', () => ({
18
+ initializeApp: mockInitializeApp,
19
+ getApps: mockGetApps,
20
+ getApp: mockGetApp,
21
+ }));
22
+ vi.mock('firebase/auth', () => ({
23
+ getAuth: mockGetAuth,
24
+ signInWithEmailAndPassword: mockSignInWithEmailAndPassword,
25
+ createUserWithEmailAndPassword: mockCreateUserWithEmailAndPassword,
26
+ signInAnonymously: mockFirebaseSignInAnonymously,
27
+ linkWithCredential: mockLinkWithCredential,
28
+ linkWithPopup: mockLinkWithPopup,
29
+ EmailAuthProvider: { credential: vi.fn(() => 'mock-credential') },
30
+ sendPasswordResetEmail: mockSendPasswordResetEmail,
31
+ signOut: mockSignOut,
32
+ onAuthStateChanged: mockOnAuthStateChanged,
33
+ }));
34
+ // Must dynamic import to allow mocks to take effect
35
+ async function freshImport() {
36
+ vi.resetModules();
37
+ return import('./firebase-client.js');
38
+ }
39
+ describe('firebase-client', () => {
40
+ beforeEach(() => {
41
+ vi.clearAllMocks();
42
+ mockGetApps.mockReturnValue([]);
43
+ mockAuthInstance.currentUser = null;
44
+ });
45
+ describe('initializeFirebase', () => {
46
+ it('creates app on first call', async () => {
47
+ const mod = await freshImport();
48
+ mod.initializeFirebase({ apiKey: 'test' });
49
+ expect(mockInitializeApp).toHaveBeenCalled();
50
+ });
51
+ it('returns existing app if already initialized', async () => {
52
+ mockGetApps.mockReturnValue([{ name: 'existing' }]);
53
+ const mod = await freshImport();
54
+ mod.initializeFirebase({ apiKey: 'test' });
55
+ expect(mockGetApp).toHaveBeenCalled();
56
+ });
57
+ });
58
+ describe('getFirebaseApp', () => {
59
+ it('throws if not initialized', async () => {
60
+ const mod = await freshImport();
61
+ expect(() => mod.getFirebaseApp()).toThrow('Firebase not initialized');
62
+ });
63
+ it('returns app after init', async () => {
64
+ const mod = await freshImport();
65
+ mod.initializeFirebase({ apiKey: 'test' });
66
+ expect(mod.getFirebaseApp()).toBeDefined();
67
+ });
68
+ });
69
+ describe('getFirebaseAuth', () => {
70
+ it('throws if not initialized', async () => {
71
+ const mod = await freshImport();
72
+ expect(() => mod.getFirebaseAuth()).toThrow('Firebase not initialized');
73
+ });
74
+ it('returns auth after init', async () => {
75
+ const mod = await freshImport();
76
+ mod.initializeFirebase({ apiKey: 'test' });
77
+ expect(mod.getFirebaseAuth()).toBeDefined();
78
+ });
79
+ });
80
+ describe('auth flows', () => {
81
+ async function initModule() {
82
+ const mod = await freshImport();
83
+ mod.initializeFirebase({ apiKey: 'test' });
84
+ return mod;
85
+ }
86
+ it('signIn delegates to signInWithEmailAndPassword', async () => {
87
+ const mod = await initModule();
88
+ await mod.signIn('a@b.com', 'pass');
89
+ expect(mockSignInWithEmailAndPassword).toHaveBeenCalledWith(expect.anything(), 'a@b.com', 'pass');
90
+ });
91
+ it('signUp delegates to createUserWithEmailAndPassword', async () => {
92
+ const mod = await initModule();
93
+ await mod.signUp('a@b.com', 'pass');
94
+ expect(mockCreateUserWithEmailAndPassword).toHaveBeenCalledWith(expect.anything(), 'a@b.com', 'pass');
95
+ });
96
+ it('signInAnonymously delegates correctly', async () => {
97
+ const mod = await initModule();
98
+ await mod.signInAnonymously();
99
+ expect(mockFirebaseSignInAnonymously).toHaveBeenCalled();
100
+ });
101
+ it('upgradeAnonymousAccount throws if no anonymous user', async () => {
102
+ const mod = await initModule();
103
+ mockAuthInstance.currentUser = null;
104
+ await expect(mod.upgradeAnonymousAccount('a@b.com', 'pass')).rejects.toThrow('No anonymous user');
105
+ });
106
+ it('upgradeAnonymousAccount links credential for anonymous user', async () => {
107
+ const mod = await initModule();
108
+ mockAuthInstance.currentUser = { isAnonymous: true };
109
+ await mod.upgradeAnonymousAccount('a@b.com', 'pass');
110
+ expect(mockLinkWithCredential).toHaveBeenCalled();
111
+ });
112
+ it('upgradeAnonymousWithPopup throws if no anonymous user', async () => {
113
+ const mod = await initModule();
114
+ mockAuthInstance.currentUser = null;
115
+ await expect(mod.upgradeAnonymousWithPopup({})).rejects.toThrow('No anonymous user');
116
+ });
117
+ it('upgradeAnonymousWithPopup links with popup for anonymous user', async () => {
118
+ const mod = await initModule();
119
+ mockAuthInstance.currentUser = { isAnonymous: true };
120
+ const provider = {};
121
+ await mod.upgradeAnonymousWithPopup(provider);
122
+ expect(mockLinkWithPopup).toHaveBeenCalledWith(expect.anything(), provider);
123
+ });
124
+ it('resetPassword delegates to sendPasswordResetEmail', async () => {
125
+ const mod = await initModule();
126
+ await mod.resetPassword('a@b.com');
127
+ expect(mockSendPasswordResetEmail).toHaveBeenCalledWith(expect.anything(), 'a@b.com');
128
+ });
129
+ it('logout delegates to signOut', async () => {
130
+ const mod = await initModule();
131
+ await mod.logout();
132
+ expect(mockSignOut).toHaveBeenCalled();
133
+ });
134
+ });
135
+ describe('session helpers', () => {
136
+ async function initModule() {
137
+ const mod = await freshImport();
138
+ mod.initializeFirebase({ apiKey: 'test' });
139
+ return mod;
140
+ }
141
+ it('onAuthChange registers callback', async () => {
142
+ const mod = await initModule();
143
+ const cb = vi.fn();
144
+ mod.onAuthChange(cb);
145
+ expect(mockOnAuthStateChanged).toHaveBeenCalledWith(expect.anything(), cb);
146
+ });
147
+ it('getCurrentUser returns null when no user', async () => {
148
+ const mod = await initModule();
149
+ mockAuthInstance.currentUser = null;
150
+ const user = await mod.getCurrentUser();
151
+ expect(user).toBeNull();
152
+ });
153
+ it('getIdToken returns null when no user', async () => {
154
+ const mod = await initModule();
155
+ mockAuthInstance.currentUser = null;
156
+ const token = await mod.getIdToken();
157
+ expect(token).toBeNull();
158
+ });
159
+ it('getIdToken returns token when user exists', async () => {
160
+ const mod = await initModule();
161
+ mockAuthInstance.currentUser = { getIdToken: vi.fn().mockResolvedValue('the-token') };
162
+ const token = await mod.getIdToken();
163
+ expect(token).toBe('the-token');
164
+ });
165
+ });
166
+ });
167
+ //# sourceMappingURL=firebase-client.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firebase-client.test.js","sourceRoot":"","sources":["../../src/lib/firebase-client.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=format-time.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-time.test.d.ts","sourceRoot":"","sources":["../../src/lib/format-time.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,41 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+ import { formatExactTime, getRelativeTime } from './format-time.js';
3
+ describe('formatExactTime', () => {
4
+ it('formats date to expected pattern', () => {
5
+ const result = formatExactTime('2026-03-15T14:30:00Z');
6
+ // Should contain time, weekday, and date parts
7
+ expect(result).toMatch(/\d{1,2}:\d{2}\s*(AM|PM)\s*\w+\s*\d+\/\d+\/\d+/);
8
+ });
9
+ });
10
+ describe('getRelativeTime', () => {
11
+ afterEach(() => {
12
+ vi.useRealTimers();
13
+ });
14
+ it('returns "Just now" for < 1 minute ago', () => {
15
+ const now = new Date();
16
+ expect(getRelativeTime(now.toISOString())).toBe('Just now');
17
+ });
18
+ it('returns "Xm ago" for minutes', () => {
19
+ const date = new Date(Date.now() - 5 * 60000);
20
+ expect(getRelativeTime(date.toISOString())).toBe('5m ago');
21
+ });
22
+ it('returns "Xh ago" for hours', () => {
23
+ const date = new Date(Date.now() - 3 * 3600000);
24
+ expect(getRelativeTime(date.toISOString())).toBe('3h ago');
25
+ });
26
+ it('returns "Xd ago" for days', () => {
27
+ const date = new Date(Date.now() - 2 * 86400000);
28
+ expect(getRelativeTime(date.toISOString())).toBe('2d ago');
29
+ });
30
+ it('returns "Xw ago" for weeks', () => {
31
+ const date = new Date(Date.now() - 14 * 86400000);
32
+ expect(getRelativeTime(date.toISOString())).toBe('2w ago');
33
+ });
34
+ it('returns formatted date for > 30 days', () => {
35
+ const date = new Date(Date.now() - 60 * 86400000);
36
+ const result = getRelativeTime(date.toISOString());
37
+ // Should be a readable date like "Jan 14, 2026"
38
+ expect(result).toMatch(/\w+ \d+, \d{4}/);
39
+ });
40
+ });
41
+ //# sourceMappingURL=format-time.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-time.test.js","sourceRoot":"","sources":["../../src/lib/format-time.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAC5D,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAEnE,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,MAAM,GAAG,eAAe,CAAC,sBAAsB,CAAC,CAAA;QACtD,+CAA+C;QAC/C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,+CAA+C,CAAC,CAAA;IACzE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,aAAa,EAAE,CAAA;IACpB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAA;QACtB,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC7D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,CAAA;QAC7C,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,CAAA;QAC/C,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAA;QAChD,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAA;QACjD,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAA;QACjD,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAA;QAClD,gDAAgD;QAChD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC1C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=linkify.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linkify.test.d.ts","sourceRoot":"","sources":["../../src/lib/linkify.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { linkifyText } from './linkify.js';
3
+ describe('linkifyText', () => {
4
+ it('converts protocol URLs to markdown links', () => {
5
+ expect(linkifyText('Visit https://example.com today')).toBe('Visit [https://example.com](https://example.com) today');
6
+ });
7
+ it('converts www URLs with https prefix', () => {
8
+ expect(linkifyText('Go to www.example.com')).toBe('Go to [www.example.com](https://www.example.com)');
9
+ });
10
+ it('converts bare domain URLs with https prefix', () => {
11
+ expect(linkifyText('Check example.com')).toBe('Check [example.com](https://example.com)');
12
+ });
13
+ it('does not linkify inside code blocks', () => {
14
+ const input = '```\nhttps://example.com\n```';
15
+ expect(linkifyText(input)).toBe(input);
16
+ });
17
+ it('does not linkify inside inline code', () => {
18
+ const input = 'Use `https://example.com` in code';
19
+ expect(linkifyText(input)).toBe(input);
20
+ });
21
+ it('does not double-linkify existing markdown links', () => {
22
+ const input = '[click here](https://example.com)';
23
+ expect(linkifyText(input)).toBe(input);
24
+ });
25
+ it('does not affect markdown images', () => {
26
+ const input = '![alt](https://example.com/img.png)';
27
+ expect(linkifyText(input)).toBe(input);
28
+ });
29
+ it('handles mixed content correctly', () => {
30
+ const input = 'Visit https://a.com and `https://b.com` and [c](https://c.com)';
31
+ const result = linkifyText(input);
32
+ expect(result).toContain('[https://a.com](https://a.com)');
33
+ expect(result).toContain('`https://b.com`');
34
+ expect(result).toContain('[c](https://c.com)');
35
+ });
36
+ it('returns text unchanged when no URLs', () => {
37
+ expect(linkifyText('Hello world')).toBe('Hello world');
38
+ });
39
+ });
40
+ //# sourceMappingURL=linkify.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linkify.test.js","sourceRoot":"","sources":["../../src/lib/linkify.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE1C,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,WAAW,CAAC,iCAAiC,CAAC,CAAC,CAAC,IAAI,CACzD,wDAAwD,CACzD,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,WAAW,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAC/C,kDAAkD,CACnD,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,CAAC,WAAW,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAC3C,0CAA0C,CAC3C,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,KAAK,GAAG,+BAA+B,CAAA;QAC7C,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,KAAK,GAAG,mCAAmC,CAAA;QACjD,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,KAAK,GAAG,mCAAmC,CAAA;QACjD,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,KAAK,GAAG,qCAAqC,CAAA;QACnD,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,KAAK,GAAG,gEAAgE,CAAA;QAC9E,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAA;QACjC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,gCAAgC,CAAC,CAAA;QAC1D,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAA;QAC3C,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAA;IAChD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;IACxD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=logger.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.test.d.ts","sourceRoot":"","sources":["../../src/lib/logger.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,167 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { sanitizeToken, sanitizeEmail, sanitizeUserId, sanitizeObject, createLogger, authLogger, apiLogger, dbLogger, chatLogger, } from './logger.js';
3
+ describe('sanitizeToken', () => {
4
+ it('returns hash prefix for valid token', () => {
5
+ const result = sanitizeToken('my-secret-token');
6
+ expect(result).toMatch(/^[0-9a-f]{8}\.\.\.$/);
7
+ });
8
+ it('returns [empty] for null', () => {
9
+ expect(sanitizeToken(null)).toBe('[empty]');
10
+ });
11
+ it('returns [empty] for undefined', () => {
12
+ expect(sanitizeToken(undefined)).toBe('[empty]');
13
+ });
14
+ it('returns [empty] for empty string', () => {
15
+ expect(sanitizeToken('')).toBe('[empty]');
16
+ });
17
+ it('is deterministic for same input', () => {
18
+ expect(sanitizeToken('abc')).toBe(sanitizeToken('abc'));
19
+ });
20
+ });
21
+ describe('sanitizeEmail', () => {
22
+ it('masks email correctly', () => {
23
+ expect(sanitizeEmail('john@example.com')).toBe('jo***@example.com');
24
+ });
25
+ it('returns [empty] for null', () => {
26
+ expect(sanitizeEmail(null)).toBe('[empty]');
27
+ });
28
+ it('returns [empty] for undefined', () => {
29
+ expect(sanitizeEmail(undefined)).toBe('[empty]');
30
+ });
31
+ it('returns [invalid] for no domain', () => {
32
+ expect(sanitizeEmail('nodomain')).toBe('[invalid]');
33
+ });
34
+ });
35
+ describe('sanitizeUserId', () => {
36
+ it('returns user_ prefix with first 8 chars', () => {
37
+ expect(sanitizeUserId('abcdefghijklmnop')).toBe('user_abcdefgh');
38
+ });
39
+ it('returns [empty] for null', () => {
40
+ expect(sanitizeUserId(null)).toBe('[empty]');
41
+ });
42
+ it('handles short user IDs', () => {
43
+ expect(sanitizeUserId('ab')).toBe('user_ab');
44
+ });
45
+ });
46
+ describe('sanitizeObject', () => {
47
+ it('redacts fields containing token', () => {
48
+ expect(sanitizeObject({ accessToken: 'secret' })).toEqual({ accessToken: '[REDACTED]' });
49
+ });
50
+ it('redacts fields containing password', () => {
51
+ expect(sanitizeObject({ password: '123' })).toEqual({ password: '[REDACTED]' });
52
+ });
53
+ it('redacts fields containing secret', () => {
54
+ expect(sanitizeObject({ clientSecret: 'abc' })).toEqual({ clientSecret: '[REDACTED]' });
55
+ });
56
+ it('redacts fields containing key', () => {
57
+ expect(sanitizeObject({ apiKey: 'xyz' })).toEqual({ apiKey: '[REDACTED]' });
58
+ });
59
+ it('redacts fields containing authorization', () => {
60
+ expect(sanitizeObject({ authorization: 'Bearer xxx' })).toEqual({ authorization: '[REDACTED]' });
61
+ });
62
+ it('redacts fields containing credential', () => {
63
+ expect(sanitizeObject({ credential: 'cred' })).toEqual({ credential: '[REDACTED]' });
64
+ });
65
+ it('recursively sanitizes nested objects', () => {
66
+ const input = { user: { name: 'John', password: 'secret' } };
67
+ expect(sanitizeObject(input)).toEqual({ user: { name: 'John', password: '[REDACTED]' } });
68
+ });
69
+ it('handles arrays', () => {
70
+ const input = [{ token: 'abc' }, { name: 'ok' }];
71
+ expect(sanitizeObject(input)).toEqual([{ token: '[REDACTED]' }, { name: 'ok' }]);
72
+ });
73
+ it('returns non-objects as-is', () => {
74
+ expect(sanitizeObject('hello')).toBe('hello');
75
+ expect(sanitizeObject(42)).toBe(42);
76
+ expect(sanitizeObject(null)).toBeNull();
77
+ });
78
+ it('leaves safe fields untouched', () => {
79
+ expect(sanitizeObject({ name: 'John', age: 30 })).toEqual({ name: 'John', age: 30 });
80
+ });
81
+ });
82
+ describe('createLogger', () => {
83
+ beforeEach(() => {
84
+ vi.spyOn(console, 'log').mockImplementation(() => { });
85
+ vi.spyOn(console, 'warn').mockImplementation(() => { });
86
+ vi.spyOn(console, 'error').mockImplementation(() => { });
87
+ vi.spyOn(console, 'debug').mockImplementation(() => { });
88
+ });
89
+ afterEach(() => {
90
+ vi.restoreAllMocks();
91
+ });
92
+ it('returns logger with debug/info/warn/error methods', () => {
93
+ const logger = createLogger('Test');
94
+ expect(typeof logger.debug).toBe('function');
95
+ expect(typeof logger.info).toBe('function');
96
+ expect(typeof logger.warn).toBe('function');
97
+ expect(typeof logger.error).toBe('function');
98
+ });
99
+ it('info calls console.log with context prefix', () => {
100
+ const logger = createLogger('MyCtx');
101
+ logger.info('hello');
102
+ expect(console.log).toHaveBeenCalledWith('[MyCtx]', 'hello', '');
103
+ });
104
+ it('warn calls console.warn', () => {
105
+ const logger = createLogger('W');
106
+ logger.warn('warning');
107
+ expect(console.warn).toHaveBeenCalledWith('[W]', 'warning', '');
108
+ });
109
+ it('error calls console.error with error message', () => {
110
+ const logger = createLogger('E');
111
+ const err = new Error('boom');
112
+ logger.error('failed', err);
113
+ expect(console.error).toHaveBeenCalled();
114
+ const call = console.error.mock.calls[0];
115
+ expect(call[0]).toBe('[E]');
116
+ expect(call[1]).toBe('failed');
117
+ });
118
+ it('debug does not log when NODE_ENV is not development', () => {
119
+ const original = process.env.NODE_ENV;
120
+ process.env.NODE_ENV = 'production';
121
+ const logger = createLogger('D');
122
+ logger.debug('test');
123
+ expect(console.debug).not.toHaveBeenCalled();
124
+ process.env.NODE_ENV = original;
125
+ });
126
+ it('debug logs when NODE_ENV is development', () => {
127
+ const original = process.env.NODE_ENV;
128
+ process.env.NODE_ENV = 'development';
129
+ const logger = createLogger('D');
130
+ logger.debug('test');
131
+ expect(console.debug).toHaveBeenCalled();
132
+ process.env.NODE_ENV = original;
133
+ });
134
+ it('sanitizes data before output', () => {
135
+ const logger = createLogger('S');
136
+ logger.info('data', { password: 'secret', name: 'ok' });
137
+ const call = console.log.mock.calls[0];
138
+ expect(call[2]).toEqual({ password: '[REDACTED]', name: 'ok' });
139
+ });
140
+ });
141
+ describe('pre-configured loggers', () => {
142
+ it('authLogger has Auth context', () => {
143
+ vi.spyOn(console, 'log').mockImplementation(() => { });
144
+ authLogger.info('test');
145
+ expect(console.log).toHaveBeenCalledWith('[Auth]', 'test', '');
146
+ vi.restoreAllMocks();
147
+ });
148
+ it('apiLogger has API context', () => {
149
+ vi.spyOn(console, 'log').mockImplementation(() => { });
150
+ apiLogger.info('test');
151
+ expect(console.log).toHaveBeenCalledWith('[API]', 'test', '');
152
+ vi.restoreAllMocks();
153
+ });
154
+ it('dbLogger has Database context', () => {
155
+ vi.spyOn(console, 'log').mockImplementation(() => { });
156
+ dbLogger.info('test');
157
+ expect(console.log).toHaveBeenCalledWith('[Database]', 'test', '');
158
+ vi.restoreAllMocks();
159
+ });
160
+ it('chatLogger has Chat context', () => {
161
+ vi.spyOn(console, 'log').mockImplementation(() => { });
162
+ chatLogger.info('test');
163
+ expect(console.log).toHaveBeenCalledWith('[Chat]', 'test', '');
164
+ vi.restoreAllMocks();
165
+ });
166
+ });
167
+ //# sourceMappingURL=logger.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.test.js","sourceRoot":"","sources":["../../src/lib/logger.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rate-limiter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.test.d.ts","sourceRoot":"","sources":["../../src/lib/rate-limiter.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,70 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { checkRateLimit, createRateLimitResponse, getRateLimitIdentifier } from './rate-limiter.js';
3
+ // Suppress console.error in tests
4
+ vi.spyOn(console, 'error').mockImplementation(() => { });
5
+ const config = { limit: 100, period: 60, keyPrefix: 'api' };
6
+ describe('checkRateLimit', () => {
7
+ it('returns success when limiter allows', async () => {
8
+ const limiter = { limit: vi.fn().mockResolvedValue({ success: true, limit: 100, remaining: 99 }) };
9
+ const result = await checkRateLimit(limiter, 'user:1', config);
10
+ expect(result.success).toBe(true);
11
+ expect(result.remaining).toBe(99);
12
+ });
13
+ it('returns failure when limiter denies', async () => {
14
+ const limiter = { limit: vi.fn().mockResolvedValue({ success: false, limit: 100, remaining: 0, retryAfter: 30 }) };
15
+ const result = await checkRateLimit(limiter, 'user:1', config);
16
+ expect(result.success).toBe(false);
17
+ expect(result.retryAfter).toBe(30);
18
+ });
19
+ it('fails open on limiter error', async () => {
20
+ const limiter = { limit: vi.fn().mockRejectedValue(new Error('down')) };
21
+ const result = await checkRateLimit(limiter, 'user:1', config);
22
+ expect(result.success).toBe(true);
23
+ expect(result.limit).toBe(100);
24
+ });
25
+ it('constructs key from prefix + identifier', async () => {
26
+ const limiter = { limit: vi.fn().mockResolvedValue({ success: true, limit: 100, remaining: 99 }) };
27
+ await checkRateLimit(limiter, 'user:abc', config);
28
+ expect(limiter.limit).toHaveBeenCalledWith({ key: 'api:user:abc' });
29
+ });
30
+ });
31
+ describe('createRateLimitResponse', () => {
32
+ it('returns 429 status', () => {
33
+ const res = createRateLimitResponse({ success: false, limit: 100, remaining: 0, retryAfter: 60 });
34
+ expect(res.status).toBe(429);
35
+ });
36
+ it('includes Retry-After header', () => {
37
+ const res = createRateLimitResponse({ success: false, limit: 100, remaining: 0, retryAfter: 30 });
38
+ expect(res.headers.get('Retry-After')).toBe('30');
39
+ });
40
+ it('includes X-RateLimit headers', () => {
41
+ const res = createRateLimitResponse({ success: false, limit: 100, remaining: 5, retryAfter: 60 });
42
+ expect(res.headers.get('X-RateLimit-Limit')).toBe('100');
43
+ expect(res.headers.get('X-RateLimit-Remaining')).toBe('5');
44
+ });
45
+ it('body contains error message JSON', async () => {
46
+ const res = createRateLimitResponse({ success: false, limit: 100, remaining: 0, retryAfter: 60 });
47
+ const body = await res.json();
48
+ expect(body.error).toBe('Too many requests');
49
+ expect(body.retryAfter).toBe(60);
50
+ });
51
+ });
52
+ describe('getRateLimitIdentifier', () => {
53
+ it('returns user:{userId} when userId provided', () => {
54
+ const req = new Request('https://x.com');
55
+ expect(getRateLimitIdentifier(req, 'u123')).toBe('user:u123');
56
+ });
57
+ it('returns ip from cf-connecting-ip header', () => {
58
+ const req = new Request('https://x.com', { headers: { 'cf-connecting-ip': '1.2.3.4' } });
59
+ expect(getRateLimitIdentifier(req)).toBe('ip:1.2.3.4');
60
+ });
61
+ it('falls back to x-forwarded-for', () => {
62
+ const req = new Request('https://x.com', { headers: { 'x-forwarded-for': '5.6.7.8' } });
63
+ expect(getRateLimitIdentifier(req)).toBe('ip:5.6.7.8');
64
+ });
65
+ it('returns ip:unknown when no headers', () => {
66
+ const req = new Request('https://x.com');
67
+ expect(getRateLimitIdentifier(req)).toBe('ip:unknown');
68
+ });
69
+ });
70
+ //# sourceMappingURL=rate-limiter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.test.js","sourceRoot":"","sources":["../../src/lib/rate-limiter.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAEnG,kCAAkC;AAClC,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;AAEvD,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;AAE3D,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACnD,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,EAAE,CAAA;QAClG,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;QAC9D,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACnC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACnD,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,EAAE,CAAA;QAClH,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;QAC9D,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAClC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACpC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC3C,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAA;QACvE,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;QAC9D,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAChC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,EAAE,CAAA;QAClG,MAAM,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,CAAA;QACjD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,oBAAoB,CAAC,EAAE,GAAG,EAAE,cAAc,EAAE,CAAC,CAAA;IACrE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC5B,MAAM,GAAG,GAAG,uBAAuB,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAA;QACjG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC9B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,GAAG,GAAG,uBAAuB,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAA;QACjG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACnD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,GAAG,GAAG,uBAAuB,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAA;QACjG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACxD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;QAChD,MAAM,GAAG,GAAG,uBAAuB,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAA;QACjG,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;QAC7B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;QAC5C,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAClC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,eAAe,CAAC,CAAA;QACxC,MAAM,CAAC,sBAAsB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAC/D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,eAAe,EAAE,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAAE,SAAS,EAAE,EAAE,CAAC,CAAA;QACxF,MAAM,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IACxD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,eAAe,EAAE,EAAE,OAAO,EAAE,EAAE,iBAAiB,EAAE,SAAS,EAAE,EAAE,CAAC,CAAA;QACvF,MAAM,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IACxD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,eAAe,CAAC,CAAA;QACxC,MAAM,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IACxD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=uuid.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uuid.test.d.ts","sourceRoot":"","sources":["../../src/lib/uuid.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generateUUID } from './uuid.js';
3
+ describe('generateUUID', () => {
4
+ it('returns valid UUID v4 format', () => {
5
+ const uuid = generateUUID();
6
+ expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
7
+ });
8
+ it('version nibble is 4', () => {
9
+ const uuid = generateUUID();
10
+ expect(uuid[14]).toBe('4');
11
+ });
12
+ it('variant bits are correct (8, 9, a, or b)', () => {
13
+ const uuid = generateUUID();
14
+ expect(['8', '9', 'a', 'b']).toContain(uuid[19]);
15
+ });
16
+ it('two calls return different values', () => {
17
+ const a = generateUUID();
18
+ const b = generateUUID();
19
+ expect(a).not.toBe(b);
20
+ });
21
+ });
22
+ //# sourceMappingURL=uuid.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uuid.test.js","sourceRoot":"","sources":["../../src/lib/uuid.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAExC,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,IAAI,GAAG,YAAY,EAAE,CAAA;QAC3B,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,uEAAuE,CAAC,CAAA;IAC/F,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,IAAI,GAAG,YAAY,EAAE,CAAA;QAC3B,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,IAAI,GAAG,YAAY,EAAE,CAAA;QAC3B,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,CAAC,GAAG,YAAY,EAAE,CAAA;QACxB,MAAM,CAAC,GAAG,YAAY,EAAE,CAAA;QACxB,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACvB,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=base.service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.service.test.d.ts","sourceRoot":"","sources":["../../src/services/base.service.test.ts"],"names":[],"mappings":""}