@tuturuuu/utils 0.0.2 → 0.6.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 (186) hide show
  1. package/CHANGELOG.md +305 -0
  2. package/biome.json +5 -0
  3. package/jsr.json +8 -8
  4. package/package.json +63 -32
  5. package/src/__tests__/ai-temp-auth.test.ts +309 -0
  6. package/src/__tests__/api-proxy-guard.test.ts +1451 -0
  7. package/src/__tests__/app-url.test.ts +270 -0
  8. package/src/__tests__/avatar-url.test.ts +97 -0
  9. package/src/__tests__/color-helper.test.ts +179 -0
  10. package/src/__tests__/constants.test.ts +351 -0
  11. package/src/__tests__/crypto.test.ts +107 -0
  12. package/src/__tests__/date-helper.test.ts +408 -0
  13. package/src/__tests__/fixtures/task-description-full-featured.json +456 -0
  14. package/src/__tests__/format.test.ts +317 -0
  15. package/src/__tests__/html-sanitizer.test.ts +360 -0
  16. package/src/__tests__/interest-calculator.test.ts +336 -0
  17. package/src/__tests__/interest-detector.test.ts +222 -0
  18. package/src/__tests__/label-colors.test.ts +241 -0
  19. package/src/__tests__/name-helper.test.ts +158 -0
  20. package/src/__tests__/node-diff.test.ts +576 -0
  21. package/src/__tests__/notification-service.test.ts +210 -0
  22. package/src/__tests__/onboarding-helper.test.ts +331 -0
  23. package/src/__tests__/path-helper.test.ts +152 -0
  24. package/src/__tests__/permissions.test.tsx +81 -0
  25. package/src/__tests__/request-emoji-limit.test.ts +172 -0
  26. package/src/__tests__/search-helper.test.ts +51 -0
  27. package/src/__tests__/storage-display-name.test.ts +37 -0
  28. package/src/__tests__/storage-path.test.ts +238 -0
  29. package/src/__tests__/tag-utils.test.ts +205 -0
  30. package/src/__tests__/task-description-yjs-state.test.ts +581 -0
  31. package/src/__tests__/task-helper-board-api-routing.test.ts +94 -0
  32. package/src/__tests__/task-helper-create-task.test.ts +129 -0
  33. package/src/__tests__/task-helpers.test.ts +464 -0
  34. package/src/__tests__/task-overrides.test.ts +305 -0
  35. package/src/__tests__/task-reorder-cache.test.ts +74 -0
  36. package/src/__tests__/task-sort-keys.test.ts +36 -0
  37. package/src/__tests__/task-transformers.test.ts +62 -0
  38. package/src/__tests__/text-helper.test.ts +776 -0
  39. package/src/__tests__/time-helper.test.ts +70 -0
  40. package/src/__tests__/time-tracker-period.test.ts +55 -0
  41. package/src/__tests__/timezone.test.ts +117 -0
  42. package/src/__tests__/upstash-rest.test.ts +77 -0
  43. package/src/__tests__/uuid-helper.test.ts +133 -0
  44. package/src/__tests__/workspace-helper.test.ts +859 -0
  45. package/src/__tests__/workspace-limits.test.ts +255 -0
  46. package/src/__tests__/yjs-helper.test.ts +581 -0
  47. package/src/abuse-protection/__tests__/backend-rate-limit.test.ts +113 -0
  48. package/src/abuse-protection/__tests__/edge.test.ts +136 -0
  49. package/src/abuse-protection/__tests__/index.test.ts +562 -0
  50. package/src/abuse-protection/__tests__/reputation.test.ts +192 -0
  51. package/src/abuse-protection/backend-rate-limit.ts +44 -0
  52. package/src/abuse-protection/constants.ts +117 -0
  53. package/src/abuse-protection/edge.ts +223 -0
  54. package/src/abuse-protection/index.ts +1545 -0
  55. package/src/abuse-protection/reputation.ts +587 -0
  56. package/src/abuse-protection/types.ts +97 -0
  57. package/src/abuse-protection/user-agent.ts +124 -0
  58. package/src/abuse-protection/user-suspension.ts +231 -0
  59. package/src/ai-temp-auth.ts +315 -0
  60. package/src/api-proxy-guard.ts +965 -0
  61. package/src/app-url.ts +96 -0
  62. package/src/avatar-url.ts +64 -0
  63. package/src/break-duration.ts +84 -0
  64. package/src/calendar-auth-token.test.ts +37 -0
  65. package/src/calendar-auth-token.ts +19 -0
  66. package/src/calendar-sync-coordination.md +197 -0
  67. package/src/calendar-utils.test.ts +169 -0
  68. package/src/calendar-utils.ts +91 -0
  69. package/src/color-helper.ts +110 -0
  70. package/src/common/nextjs.tsx +99 -0
  71. package/src/common/scan.tsx +15 -0
  72. package/src/configs/reports.ts +160 -0
  73. package/src/constants.ts +85 -0
  74. package/src/crypto.ts +21 -0
  75. package/src/currencies.ts +97 -0
  76. package/src/date-helper.ts +313 -0
  77. package/src/editor/convert-to-task.ts +264 -0
  78. package/src/editor/index.ts +5 -0
  79. package/src/email/__tests__/client.test.ts +141 -0
  80. package/src/email/__tests__/validation.test.ts +46 -0
  81. package/src/email/client.ts +92 -0
  82. package/src/email/server.ts +128 -0
  83. package/src/email/validation.ts +11 -0
  84. package/src/encryption/__tests__/calendar-events.test.ts +411 -0
  85. package/src/encryption/__tests__/configuration.test.ts +114 -0
  86. package/src/encryption/__tests__/field-encryption.test.ts +232 -0
  87. package/src/encryption/__tests__/key-generation.test.ts +30 -0
  88. package/src/encryption/__tests__/performance-edge-cases.test.ts +187 -0
  89. package/src/encryption/__tests__/test-helpers.ts +22 -0
  90. package/src/encryption/__tests__/workspace-key-encryption.test.ts +129 -0
  91. package/src/encryption/encryption-service.ts +343 -0
  92. package/src/encryption/index.ts +25 -0
  93. package/src/encryption/types.ts +57 -0
  94. package/src/exchange-rates.ts +49 -0
  95. package/src/feature-flags/__tests__/feature-flags.test.ts +302 -0
  96. package/src/feature-flags/core.ts +322 -0
  97. package/src/feature-flags/data.ts +16 -0
  98. package/src/feature-flags/default.ts +18 -0
  99. package/src/feature-flags/index.ts +7 -0
  100. package/src/feature-flags/requestable-features.ts +79 -0
  101. package/src/feature-flags/types.ts +4 -0
  102. package/src/fetcher.ts +2 -0
  103. package/src/finance/index.ts +4 -0
  104. package/src/finance/interest-calculator.ts +456 -0
  105. package/src/finance/interest-detector.ts +141 -0
  106. package/src/finance/transform-invoice-results.ts +219 -0
  107. package/src/finance/wallet-permissions.test.ts +169 -0
  108. package/src/finance/wallet-permissions.ts +82 -0
  109. package/src/format.ts +122 -3
  110. package/src/generated/platform-build-metadata.ts +11 -0
  111. package/src/hooks/use-platform.ts +64 -0
  112. package/src/html-sanitizer.ts +155 -0
  113. package/src/internal-domains.ts +497 -0
  114. package/src/keyboard-preset.ts +109 -0
  115. package/src/label-colors.ts +213 -0
  116. package/src/launchable-apps.test.ts +126 -0
  117. package/src/launchable-apps.ts +490 -0
  118. package/src/name-helper.ts +269 -0
  119. package/src/next-config.test.ts +234 -0
  120. package/src/next-config.ts +203 -0
  121. package/src/node-diff.ts +375 -0
  122. package/src/notification-service.ts +379 -0
  123. package/src/nova/scores/__tests__/calculate.test.ts +254 -0
  124. package/src/nova/scores/calculate.ts +132 -0
  125. package/src/nova/submissions/check-permission.ts +132 -0
  126. package/src/onboarding-helper.ts +213 -0
  127. package/src/path-helper.ts +93 -0
  128. package/src/permissions.tsx +1170 -0
  129. package/src/plan-helpers.test.ts +188 -0
  130. package/src/plan-helpers.ts +80 -0
  131. package/src/platform-release.test.ts +74 -0
  132. package/src/platform-release.ts +155 -0
  133. package/src/portless.ts +124 -0
  134. package/src/priority-styles.ts +42 -0
  135. package/src/request-emoji-limit.ts +335 -0
  136. package/src/search-helper.ts +18 -0
  137. package/src/search.test.ts +89 -0
  138. package/src/search.ts +355 -0
  139. package/src/storage-display-name.ts +30 -0
  140. package/src/storage-path.ts +147 -0
  141. package/src/tag-utils.ts +159 -0
  142. package/src/task/reorder.ts +245 -0
  143. package/src/task/transformers.ts +149 -0
  144. package/src/task-date-timezone.ts +133 -0
  145. package/src/task-description-content.ts +240 -0
  146. package/src/task-helper/board.ts +193 -0
  147. package/src/task-helper/bulk-actions.ts +564 -0
  148. package/src/task-helper/personal-external-staging.ts +21 -0
  149. package/src/task-helper/recycle-bin.ts +202 -0
  150. package/src/task-helper/relationships.ts +346 -0
  151. package/src/task-helper/shared.ts +109 -0
  152. package/src/task-helper/sort-keys.ts +337 -0
  153. package/src/task-helper/task-hooks-basic.ts +342 -0
  154. package/src/task-helper/task-hooks-move.ts +264 -0
  155. package/src/task-helper/task-operations.ts +278 -0
  156. package/src/task-helper.ts +12 -0
  157. package/src/task-helpers.ts +241 -0
  158. package/src/task-list-status.ts +62 -0
  159. package/src/task-overrides.ts +82 -0
  160. package/src/task-snapshot.ts +374 -0
  161. package/src/text-diff.ts +81 -0
  162. package/src/text-helper.ts +537 -0
  163. package/src/time-helper.ts +63 -0
  164. package/src/time-tracker-period.ts +73 -0
  165. package/src/timeblock-helper.ts +418 -0
  166. package/src/timezone.ts +190 -0
  167. package/src/timezones.json +1271 -0
  168. package/src/upstash-rest.ts +56 -0
  169. package/src/user-helper.ts +296 -0
  170. package/src/uuid-helper.ts +11 -0
  171. package/src/workspace-handle.ts +10 -0
  172. package/src/workspace-helper.ts +1408 -0
  173. package/src/workspace-limits.ts +68 -0
  174. package/src/yjs-helper.ts +217 -0
  175. package/src/yjs-task-description.ts +81 -0
  176. package/tsconfig.json +3 -5
  177. package/tsconfig.typecheck.json +33 -0
  178. package/vitest.config.ts +36 -0
  179. package/dist/index.d.ts +0 -8
  180. package/dist/index.js +0 -2
  181. package/dist/index.js.map +0 -1
  182. package/dist/index.mjs +0 -2
  183. package/dist/index.mjs.map +0 -1
  184. package/eslint.config.mjs +0 -20
  185. package/rollup.config.js +0 -41
  186. package/src/index.ts +0 -1
@@ -0,0 +1,70 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { parseTimeFromTimetz } from '../time-helper';
3
+
4
+ describe('Time Helper', () => {
5
+ describe('parseTimeFromTimetz', () => {
6
+ it('parses valid timetz format', () => {
7
+ expect(parseTimeFromTimetz('09:00:00+00')).toBe(9);
8
+ });
9
+
10
+ it('parses midnight as 24', () => {
11
+ expect(parseTimeFromTimetz('00:00:00+00')).toBe(24);
12
+ });
13
+
14
+ it('parses single digit hour', () => {
15
+ expect(parseTimeFromTimetz('5:00:00+00')).toBe(5);
16
+ });
17
+
18
+ it('parses hour 23', () => {
19
+ expect(parseTimeFromTimetz('23:00:00+00')).toBe(23);
20
+ });
21
+
22
+ it('parses hour 12', () => {
23
+ expect(parseTimeFromTimetz('12:00:00+00')).toBe(12);
24
+ });
25
+
26
+ it('parses hour 1', () => {
27
+ expect(parseTimeFromTimetz('01:00:00+00')).toBe(1);
28
+ });
29
+
30
+ it('handles different timezone offsets', () => {
31
+ expect(parseTimeFromTimetz('15:30:00+05:30')).toBe(15);
32
+ });
33
+
34
+ it('handles negative timezone offset', () => {
35
+ expect(parseTimeFromTimetz('08:00:00-08')).toBe(8);
36
+ });
37
+
38
+ it('returns undefined for undefined input', () => {
39
+ expect(parseTimeFromTimetz(undefined)).toBeUndefined();
40
+ });
41
+
42
+ it('returns undefined for empty string', () => {
43
+ expect(parseTimeFromTimetz('')).toBeUndefined();
44
+ });
45
+
46
+ it('returns undefined for invalid format without colon', () => {
47
+ expect(parseTimeFromTimetz('invalid')).toBeUndefined();
48
+ });
49
+
50
+ it('returns undefined for invalid hour (24)', () => {
51
+ expect(parseTimeFromTimetz('24:00:00+00')).toBeUndefined();
52
+ });
53
+
54
+ it('returns undefined for negative hour', () => {
55
+ expect(parseTimeFromTimetz('-1:00:00+00')).toBeUndefined();
56
+ });
57
+
58
+ it('returns undefined for non-numeric hour', () => {
59
+ expect(parseTimeFromTimetz('abc:00:00+00')).toBeUndefined();
60
+ });
61
+
62
+ it('handles time without timezone', () => {
63
+ expect(parseTimeFromTimetz('14:30:00')).toBe(14);
64
+ });
65
+
66
+ it('handles just hour:minute format', () => {
67
+ expect(parseTimeFromTimetz('10:30')).toBe(10);
68
+ });
69
+ });
70
+ });
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ formatTimeTrackerDateRange,
4
+ getTimeTrackerPeriodBounds,
5
+ } from '../time-tracker-period';
6
+
7
+ describe('time-tracker-period', () => {
8
+ it('builds ISO week boundaries in UTC', () => {
9
+ const date = new Date('2024-02-07T10:30:00.000Z');
10
+ const { startOfPeriod, endOfPeriod } = getTimeTrackerPeriodBounds(
11
+ date,
12
+ 'week',
13
+ 'UTC'
14
+ );
15
+
16
+ expect(startOfPeriod.toISOString()).toBe('2024-02-05T00:00:00.000Z');
17
+ expect(endOfPeriod.toISOString()).toBe('2024-02-11T23:59:59.999Z');
18
+ });
19
+
20
+ it('formats a week range with a consistent locale', () => {
21
+ const start = new Date('2024-02-05T00:00:00.000Z');
22
+ const end = new Date('2024-02-11T23:59:59.999Z');
23
+ const expected = `${start.toLocaleDateString('en-US', {
24
+ month: 'short',
25
+ day: 'numeric',
26
+ })} - ${end.toLocaleDateString('en-US', {
27
+ month: 'short',
28
+ day: 'numeric',
29
+ year: 'numeric',
30
+ })}`;
31
+
32
+ expect(
33
+ formatTimeTrackerDateRange(start, end, 'week', {
34
+ locale: 'en-US',
35
+ referenceDate: new Date('2023-01-01T00:00:00.000Z'),
36
+ })
37
+ ).toBe(expected);
38
+ });
39
+
40
+ it('formats a day range without a year in the same year', () => {
41
+ const start = new Date('2024-03-12T09:00:00.000Z');
42
+ const expected = start.toLocaleDateString('en-US', {
43
+ weekday: 'long',
44
+ month: 'long',
45
+ day: 'numeric',
46
+ });
47
+
48
+ expect(
49
+ formatTimeTrackerDateRange(start, start, 'day', {
50
+ locale: 'en-US',
51
+ referenceDate: new Date('2024-01-01T00:00:00.000Z'),
52
+ })
53
+ ).toBe(expected);
54
+ });
55
+ });
@@ -0,0 +1,117 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import {
3
+ AUTO_TIMEZONE,
4
+ getBrowserTimezone,
5
+ getTimezoneOffset,
6
+ isValidTimezone,
7
+ resolveAutoTimezone,
8
+ } from '../timezone';
9
+
10
+ describe('timezone utilities', () => {
11
+ describe('isValidTimezone', () => {
12
+ it('should return true for valid IANA timezone identifiers', () => {
13
+ // Test commonly supported timezones
14
+ expect(isValidTimezone('America/New_York')).toBe(true);
15
+ expect(isValidTimezone('Europe/London')).toBe(true);
16
+ expect(isValidTimezone('UTC')).toBe(true);
17
+ // Note: Some test environments may not support all IANA timezones
18
+ // through the Intl.DateTimeFormat fallback
19
+ });
20
+
21
+ it('should return false for invalid timezone strings', () => {
22
+ expect(isValidTimezone('Invalid/Timezone')).toBe(false);
23
+ expect(isValidTimezone('')).toBe(false);
24
+ expect(isValidTimezone('random-string')).toBe(false);
25
+ // Note: PST/EST may be accepted by some Node.js versions as legacy abbreviations
26
+ // but they're not proper IANA identifiers - behavior may vary by environment
27
+ });
28
+
29
+ it('should return false for null, undefined, or non-string values', () => {
30
+ expect(isValidTimezone(null as unknown as string)).toBe(false);
31
+ expect(isValidTimezone(undefined as unknown as string)).toBe(false);
32
+ expect(isValidTimezone(123 as unknown as string)).toBe(false);
33
+ });
34
+ });
35
+
36
+ describe('getBrowserTimezone', () => {
37
+ it('should return a valid timezone string', () => {
38
+ const tz = getBrowserTimezone();
39
+ expect(typeof tz).toBe('string');
40
+ expect(tz.length).toBeGreaterThan(0);
41
+ // Should be a valid timezone
42
+ expect(isValidTimezone(tz)).toBe(true);
43
+ });
44
+
45
+ it('should fallback to UTC when Intl is unavailable', () => {
46
+ const originalIntl = globalThis.Intl;
47
+ // @ts-expect-error - Intentionally removing Intl for testing
48
+ globalThis.Intl = undefined;
49
+
50
+ const tz = getBrowserTimezone();
51
+ expect(tz).toBe('UTC');
52
+
53
+ globalThis.Intl = originalIntl;
54
+ });
55
+ });
56
+
57
+ describe('resolveAutoTimezone', () => {
58
+ let mockBrowserTimezone: string;
59
+
60
+ beforeEach(() => {
61
+ // Store the actual browser timezone
62
+ mockBrowserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
63
+ });
64
+
65
+ it('should resolve "auto" to browser timezone', () => {
66
+ const result = resolveAutoTimezone('auto');
67
+ expect(result).toBe(mockBrowserTimezone);
68
+ });
69
+
70
+ it('should resolve AUTO_TIMEZONE constant to browser timezone', () => {
71
+ const result = resolveAutoTimezone(AUTO_TIMEZONE);
72
+ expect(result).toBe(mockBrowserTimezone);
73
+ });
74
+
75
+ it('should return valid timezone as-is', () => {
76
+ expect(resolveAutoTimezone('America/New_York')).toBe('America/New_York');
77
+ expect(resolveAutoTimezone('UTC')).toBe('UTC');
78
+ expect(resolveAutoTimezone('Europe/London')).toBe('Europe/London');
79
+ });
80
+
81
+ it('should fallback to UTC for invalid timezone', () => {
82
+ expect(resolveAutoTimezone('Invalid/Timezone')).toBe('UTC');
83
+ expect(resolveAutoTimezone('random-string')).toBe('UTC');
84
+ });
85
+
86
+ it('should fallback to UTC for null or undefined', () => {
87
+ expect(resolveAutoTimezone(null)).toBe('UTC');
88
+ expect(resolveAutoTimezone(undefined)).toBe('UTC');
89
+ });
90
+ });
91
+
92
+ describe('getTimezoneOffset', () => {
93
+ it('should return correct offset for known timezones', () => {
94
+ // Note: These tests may fail during DST transitions
95
+ // We test format rather than exact values since offsets change with DST
96
+ const utcOffset = getTimezoneOffset('UTC');
97
+ expect(utcOffset).toBe('+00:00');
98
+
99
+ const hoChiMinhOffset = getTimezoneOffset('Asia/Ho_Chi_Minh');
100
+ expect(hoChiMinhOffset).toBe('+07:00');
101
+
102
+ const tokyoOffset = getTimezoneOffset('Asia/Tokyo');
103
+ expect(tokyoOffset).toBe('+09:00');
104
+ });
105
+
106
+ it('should return offset in correct format', () => {
107
+ const offset = getTimezoneOffset('America/New_York');
108
+ // Should match format +HH:MM or -HH:MM
109
+ expect(offset).toMatch(/^[+-]\d{2}:\d{2}$/);
110
+ });
111
+
112
+ it('should fallback to +00:00 for invalid timezone', () => {
113
+ const offset = getTimezoneOffset('Invalid/Timezone');
114
+ expect(offset).toBe('+00:00');
115
+ });
116
+ });
117
+ });
@@ -0,0 +1,77 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ const mocks = vi.hoisted(() => ({
4
+ fromEnv: vi.fn(),
5
+ }));
6
+
7
+ vi.mock('@upstash/redis', () => ({
8
+ Redis: {
9
+ fromEnv: mocks.fromEnv,
10
+ },
11
+ }));
12
+
13
+ describe('upstash-rest', () => {
14
+ afterEach(() => {
15
+ vi.unstubAllEnvs();
16
+ mocks.fromEnv.mockReset();
17
+ });
18
+
19
+ it('returns null when Upstash REST env vars are missing', async () => {
20
+ const { getUpstashRestRedisClient, hasUpstashRestEnv } = await import(
21
+ '../upstash-rest.js'
22
+ );
23
+
24
+ expect(hasUpstashRestEnv()).toBe(false);
25
+ await expect(getUpstashRestRedisClient()).resolves.toBeNull();
26
+ expect(mocks.fromEnv).not.toHaveBeenCalled();
27
+ });
28
+
29
+ it('builds the Redis client from Upstash REST env vars', async () => {
30
+ vi.stubEnv('UPSTASH_REDIS_REST_URL', 'https://redis.test');
31
+ vi.stubEnv('UPSTASH_REDIS_REST_TOKEN', 'token');
32
+ const client = {
33
+ del: vi.fn(),
34
+ eval: vi.fn(),
35
+ evalsha: vi.fn(),
36
+ expire: vi.fn(),
37
+ get: vi.fn(),
38
+ incr: vi.fn(),
39
+ set: vi.fn(),
40
+ ttl: vi.fn(),
41
+ };
42
+ mocks.fromEnv.mockReturnValue(client);
43
+
44
+ const {
45
+ getUpstashRatelimitRedisClient,
46
+ getUpstashRestRedisClient,
47
+ hasUpstashRestEnv,
48
+ } = await import('../upstash-rest.js');
49
+
50
+ expect(hasUpstashRestEnv()).toBe(true);
51
+
52
+ const restClient = await getUpstashRestRedisClient();
53
+ const ratelimitClient = await getUpstashRatelimitRedisClient();
54
+
55
+ expect(restClient).not.toBeNull();
56
+ expect(ratelimitClient).not.toBeNull();
57
+
58
+ await restClient?.get('rest-key');
59
+ await restClient?.set('rest-key', 'value', { ex: 60 });
60
+ await ratelimitClient?.get('ratelimit-key');
61
+ await ratelimitClient?.set('ratelimit-key', 'value');
62
+ await ratelimitClient?.eval('return 1', ['k'], ['v']);
63
+ await ratelimitClient?.evalsha('sha', ['k'], ['v']);
64
+
65
+ expect(client.get).toHaveBeenCalledWith('rest-key');
66
+ expect(client.set).toHaveBeenCalledWith('rest-key', 'value', { ex: 60 });
67
+ expect(client.get).toHaveBeenCalledWith('ratelimit-key');
68
+ expect(client.set).toHaveBeenCalledWith(
69
+ 'ratelimit-key',
70
+ 'value',
71
+ undefined
72
+ );
73
+ expect(client.eval).toHaveBeenCalledWith('return 1', ['k'], ['v']);
74
+ expect(client.evalsha).toHaveBeenCalledWith('sha', ['k'], ['v']);
75
+ expect(mocks.fromEnv).toHaveBeenCalledTimes(2);
76
+ });
77
+ });
@@ -0,0 +1,133 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { z } from 'zod';
3
+ import { generateRandomUUID, generateUUID } from '../uuid-helper';
4
+
5
+ describe('UUID Helper', () => {
6
+ describe('generateUUID', () => {
7
+ it('generates consistent UUID for same inputs', () => {
8
+ const uuid1 = generateUUID('test-id-1');
9
+ const uuid2 = generateUUID('test-id-1');
10
+ expect(uuid1).toBe(uuid2);
11
+ });
12
+
13
+ it('generates different UUIDs for different inputs', () => {
14
+ const uuid1 = generateUUID('test-id-1');
15
+ const uuid2 = generateUUID('test-id-2');
16
+ expect(uuid1).not.toBe(uuid2);
17
+ });
18
+
19
+ it('generates valid UUID format', () => {
20
+ const uuid = generateUUID('test-id');
21
+ const uuidRegex =
22
+ /^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
23
+ expect(uuid).toMatch(uuidRegex);
24
+ });
25
+
26
+ it('handles multiple UUIDs concatenated', () => {
27
+ const uuid1 = generateUUID('id1', 'id2');
28
+ const uuid2 = generateUUID('id1', 'id2');
29
+ expect(uuid1).toBe(uuid2);
30
+ });
31
+
32
+ it('generates different UUID when order changes', () => {
33
+ const uuid1 = generateUUID('id1', 'id2');
34
+ const uuid2 = generateUUID('id2', 'id1');
35
+ expect(uuid1).not.toBe(uuid2);
36
+ });
37
+
38
+ it('handles empty string input', () => {
39
+ const uuid = generateUUID('');
40
+ expect(uuid).toBeDefined();
41
+ expect(typeof uuid).toBe('string');
42
+ });
43
+
44
+ it('handles single UUID input', () => {
45
+ const uuid = generateUUID('single-id');
46
+ const uuidRegex =
47
+ /^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
48
+ expect(uuid).toMatch(uuidRegex);
49
+ });
50
+
51
+ it('handles multiple inputs', () => {
52
+ const uuid = generateUUID('a', 'b', 'c', 'd');
53
+ expect(uuid).toBeDefined();
54
+ });
55
+ });
56
+
57
+ describe('generateRandomUUID', () => {
58
+ it('generates valid UUIDv4 format', () => {
59
+ const uuid = generateRandomUUID();
60
+ const uuidRegex =
61
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
62
+ expect(uuid).toMatch(uuidRegex);
63
+ });
64
+
65
+ it('generates different UUIDs on each call', () => {
66
+ const uuid1 = generateRandomUUID();
67
+ const uuid2 = generateRandomUUID();
68
+ expect(uuid1).not.toBe(uuid2);
69
+ });
70
+
71
+ it('generates unique UUIDs across multiple calls', () => {
72
+ const uuids = new Set<string>();
73
+ for (let i = 0; i < 100; i++) {
74
+ uuids.add(generateRandomUUID());
75
+ }
76
+ expect(uuids.size).toBe(100);
77
+ });
78
+ });
79
+
80
+ describe('Zod uuid parsing', () => {
81
+ it('accept full 0s', () => {
82
+ const result = z.uuid().safeParse('00000000-0000-0000-0000-000000000000');
83
+ expect(result.success).toBe(true);
84
+ });
85
+
86
+ it('accept full Fs', () => {
87
+ const result = z.uuid().safeParse('ffffffff-ffff-ffff-ffff-ffffffffffff');
88
+ expect(result.success).toBe(true);
89
+ });
90
+
91
+ it('reject full 0s with ending 1', () => {
92
+ const result = z.uuid().safeParse('00000000-0000-0000-0000-000000000001');
93
+ expect(result.success).toBe(false);
94
+ });
95
+
96
+ it('accept version 4 UUID', () => {
97
+ const result = z.uuid().safeParse('01234567-89ab-4cde-8f01-234567890abc');
98
+ expect(result.success).toBe(true);
99
+ });
100
+
101
+ it('reject invalid UUID format', () => {
102
+ const result = z.uuid().safeParse('invalid-uuid-format');
103
+ expect(result.success).toBe(false);
104
+ });
105
+ });
106
+
107
+ describe('Zod guid parsing', () => {
108
+ it('accept full 0s', () => {
109
+ const result = z.guid().safeParse('00000000-0000-0000-0000-000000000000');
110
+ expect(result.success).toBe(true);
111
+ });
112
+
113
+ it('accept full Fs', () => {
114
+ const result = z.guid().safeParse('ffffffff-ffff-ffff-ffff-ffffffffffff');
115
+ expect(result.success).toBe(true);
116
+ });
117
+
118
+ it('accept full 0s with ending 1', () => {
119
+ const result = z.guid().safeParse('00000000-0000-0000-0000-000000000001');
120
+ expect(result.success).toBe(true);
121
+ });
122
+
123
+ it('accept version 4 GUID', () => {
124
+ const result = z.guid().safeParse('01234567-89ab-4cde-8f01-234567890abc');
125
+ expect(result.success).toBe(true);
126
+ });
127
+
128
+ it('reject invalid GUID format', () => {
129
+ const result = z.guid().safeParse('invalid-guid-format');
130
+ expect(result.success).toBe(false);
131
+ });
132
+ });
133
+ });