@tuturuuu/utils 0.0.3 → 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.
- package/CHANGELOG.md +305 -0
- package/biome.json +5 -0
- package/jsr.json +8 -8
- package/package.json +63 -32
- package/src/__tests__/ai-temp-auth.test.ts +309 -0
- package/src/__tests__/api-proxy-guard.test.ts +1451 -0
- package/src/__tests__/app-url.test.ts +270 -0
- package/src/__tests__/avatar-url.test.ts +97 -0
- package/src/__tests__/color-helper.test.ts +179 -0
- package/src/__tests__/constants.test.ts +351 -0
- package/src/__tests__/crypto.test.ts +107 -0
- package/src/__tests__/date-helper.test.ts +408 -0
- package/src/__tests__/fixtures/task-description-full-featured.json +456 -0
- package/src/__tests__/format.test.ts +317 -0
- package/src/__tests__/html-sanitizer.test.ts +360 -0
- package/src/__tests__/interest-calculator.test.ts +336 -0
- package/src/__tests__/interest-detector.test.ts +222 -0
- package/src/__tests__/label-colors.test.ts +241 -0
- package/src/__tests__/name-helper.test.ts +158 -0
- package/src/__tests__/node-diff.test.ts +576 -0
- package/src/__tests__/notification-service.test.ts +210 -0
- package/src/__tests__/onboarding-helper.test.ts +331 -0
- package/src/__tests__/path-helper.test.ts +152 -0
- package/src/__tests__/permissions.test.tsx +81 -0
- package/src/__tests__/request-emoji-limit.test.ts +172 -0
- package/src/__tests__/search-helper.test.ts +51 -0
- package/src/__tests__/storage-display-name.test.ts +37 -0
- package/src/__tests__/storage-path.test.ts +238 -0
- package/src/__tests__/tag-utils.test.ts +205 -0
- package/src/__tests__/task-description-yjs-state.test.ts +581 -0
- package/src/__tests__/task-helper-board-api-routing.test.ts +94 -0
- package/src/__tests__/task-helper-create-task.test.ts +129 -0
- package/src/__tests__/task-helpers.test.ts +464 -0
- package/src/__tests__/task-overrides.test.ts +305 -0
- package/src/__tests__/task-reorder-cache.test.ts +74 -0
- package/src/__tests__/task-sort-keys.test.ts +36 -0
- package/src/__tests__/task-transformers.test.ts +62 -0
- package/src/__tests__/text-helper.test.ts +776 -0
- package/src/__tests__/time-helper.test.ts +70 -0
- package/src/__tests__/time-tracker-period.test.ts +55 -0
- package/src/__tests__/timezone.test.ts +117 -0
- package/src/__tests__/upstash-rest.test.ts +77 -0
- package/src/__tests__/uuid-helper.test.ts +133 -0
- package/src/__tests__/workspace-helper.test.ts +859 -0
- package/src/__tests__/workspace-limits.test.ts +255 -0
- package/src/__tests__/yjs-helper.test.ts +581 -0
- package/src/abuse-protection/__tests__/backend-rate-limit.test.ts +113 -0
- package/src/abuse-protection/__tests__/edge.test.ts +136 -0
- package/src/abuse-protection/__tests__/index.test.ts +562 -0
- package/src/abuse-protection/__tests__/reputation.test.ts +192 -0
- package/src/abuse-protection/backend-rate-limit.ts +44 -0
- package/src/abuse-protection/constants.ts +117 -0
- package/src/abuse-protection/edge.ts +223 -0
- package/src/abuse-protection/index.ts +1545 -0
- package/src/abuse-protection/reputation.ts +587 -0
- package/src/abuse-protection/types.ts +97 -0
- package/src/abuse-protection/user-agent.ts +124 -0
- package/src/abuse-protection/user-suspension.ts +231 -0
- package/src/ai-temp-auth.ts +315 -0
- package/src/api-proxy-guard.ts +965 -0
- package/src/app-url.ts +96 -0
- package/src/avatar-url.ts +64 -0
- package/src/break-duration.ts +84 -0
- package/src/calendar-auth-token.test.ts +37 -0
- package/src/calendar-auth-token.ts +19 -0
- package/src/calendar-sync-coordination.md +197 -0
- package/src/calendar-utils.test.ts +169 -0
- package/src/calendar-utils.ts +91 -0
- package/src/color-helper.ts +110 -0
- package/src/common/nextjs.tsx +99 -0
- package/src/common/scan.tsx +15 -0
- package/src/configs/reports.ts +160 -0
- package/src/constants.ts +85 -0
- package/src/crypto.ts +21 -0
- package/src/currencies.ts +97 -0
- package/src/date-helper.ts +313 -0
- package/src/editor/convert-to-task.ts +264 -0
- package/src/editor/index.ts +5 -0
- package/src/email/__tests__/client.test.ts +141 -0
- package/src/email/__tests__/validation.test.ts +46 -0
- package/src/email/client.ts +92 -0
- package/src/email/server.ts +128 -0
- package/src/email/validation.ts +11 -0
- package/src/encryption/__tests__/calendar-events.test.ts +411 -0
- package/src/encryption/__tests__/configuration.test.ts +114 -0
- package/src/encryption/__tests__/field-encryption.test.ts +232 -0
- package/src/encryption/__tests__/key-generation.test.ts +30 -0
- package/src/encryption/__tests__/performance-edge-cases.test.ts +187 -0
- package/src/encryption/__tests__/test-helpers.ts +22 -0
- package/src/encryption/__tests__/workspace-key-encryption.test.ts +129 -0
- package/src/encryption/encryption-service.ts +343 -0
- package/src/encryption/index.ts +25 -0
- package/src/encryption/types.ts +57 -0
- package/src/exchange-rates.ts +49 -0
- package/src/feature-flags/__tests__/feature-flags.test.ts +302 -0
- package/src/feature-flags/core.ts +322 -0
- package/src/feature-flags/data.ts +16 -0
- package/src/feature-flags/default.ts +18 -0
- package/src/feature-flags/index.ts +7 -0
- package/src/feature-flags/requestable-features.ts +79 -0
- package/src/feature-flags/types.ts +4 -0
- package/src/fetcher.ts +2 -0
- package/src/finance/index.ts +4 -0
- package/src/finance/interest-calculator.ts +456 -0
- package/src/finance/interest-detector.ts +141 -0
- package/src/finance/transform-invoice-results.ts +219 -0
- package/src/finance/wallet-permissions.test.ts +169 -0
- package/src/finance/wallet-permissions.ts +82 -0
- package/src/format.ts +120 -1
- package/src/generated/platform-build-metadata.ts +11 -0
- package/src/hooks/use-platform.ts +64 -0
- package/src/html-sanitizer.ts +155 -0
- package/src/internal-domains.ts +497 -0
- package/src/keyboard-preset.ts +109 -0
- package/src/label-colors.ts +213 -0
- package/src/launchable-apps.test.ts +126 -0
- package/src/launchable-apps.ts +490 -0
- package/src/name-helper.ts +269 -0
- package/src/next-config.test.ts +234 -0
- package/src/next-config.ts +203 -0
- package/src/node-diff.ts +375 -0
- package/src/notification-service.ts +379 -0
- package/src/nova/scores/__tests__/calculate.test.ts +254 -0
- package/src/nova/scores/calculate.ts +132 -0
- package/src/nova/submissions/check-permission.ts +132 -0
- package/src/onboarding-helper.ts +213 -0
- package/src/path-helper.ts +93 -0
- package/src/permissions.tsx +1170 -0
- package/src/plan-helpers.test.ts +188 -0
- package/src/plan-helpers.ts +80 -0
- package/src/platform-release.test.ts +74 -0
- package/src/platform-release.ts +155 -0
- package/src/portless.ts +124 -0
- package/src/priority-styles.ts +42 -0
- package/src/request-emoji-limit.ts +335 -0
- package/src/search-helper.ts +18 -0
- package/src/search.test.ts +89 -0
- package/src/search.ts +355 -0
- package/src/storage-display-name.ts +30 -0
- package/src/storage-path.ts +147 -0
- package/src/tag-utils.ts +159 -0
- package/src/task/reorder.ts +245 -0
- package/src/task/transformers.ts +149 -0
- package/src/task-date-timezone.ts +133 -0
- package/src/task-description-content.ts +240 -0
- package/src/task-helper/board.ts +193 -0
- package/src/task-helper/bulk-actions.ts +564 -0
- package/src/task-helper/personal-external-staging.ts +21 -0
- package/src/task-helper/recycle-bin.ts +202 -0
- package/src/task-helper/relationships.ts +346 -0
- package/src/task-helper/shared.ts +109 -0
- package/src/task-helper/sort-keys.ts +337 -0
- package/src/task-helper/task-hooks-basic.ts +342 -0
- package/src/task-helper/task-hooks-move.ts +264 -0
- package/src/task-helper/task-operations.ts +278 -0
- package/src/task-helper.ts +12 -0
- package/src/task-helpers.ts +241 -0
- package/src/task-list-status.ts +62 -0
- package/src/task-overrides.ts +82 -0
- package/src/task-snapshot.ts +374 -0
- package/src/text-diff.ts +81 -0
- package/src/text-helper.ts +537 -0
- package/src/time-helper.ts +63 -0
- package/src/time-tracker-period.ts +73 -0
- package/src/timeblock-helper.ts +418 -0
- package/src/timezone.ts +190 -0
- package/src/timezones.json +1271 -0
- package/src/upstash-rest.ts +56 -0
- package/src/user-helper.ts +296 -0
- package/src/uuid-helper.ts +11 -0
- package/src/workspace-handle.ts +10 -0
- package/src/workspace-helper.ts +1408 -0
- package/src/workspace-limits.ts +68 -0
- package/src/yjs-helper.ts +217 -0
- package/src/yjs-task-description.ts +81 -0
- package/tsconfig.json +3 -5
- package/tsconfig.typecheck.json +33 -0
- package/vitest.config.ts +36 -0
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -2
- package/dist/index.mjs.map +0 -1
- package/eslint.config.mjs +0 -20
- package/rollup.config.js +0 -41
- package/src/index.ts +0 -1
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
GITHUB_OWNER,
|
|
4
|
+
GITHUB_REPO,
|
|
5
|
+
INTERNAL_WORKSPACE_SLUG,
|
|
6
|
+
isInternalWorkspaceSlug,
|
|
7
|
+
MAX_CODE_LENGTH,
|
|
8
|
+
MAX_COLOR_LENGTH,
|
|
9
|
+
MAX_DATE_STRING_LENGTH,
|
|
10
|
+
MAX_ID_LENGTH,
|
|
11
|
+
MAX_IP_LENGTH,
|
|
12
|
+
MAX_LONG_TEXT_LENGTH,
|
|
13
|
+
MAX_MEDIUM_TEXT_LENGTH,
|
|
14
|
+
MAX_NAME_LENGTH,
|
|
15
|
+
MAX_OTP_LENGTH,
|
|
16
|
+
MAX_PAYLOAD_SIZE,
|
|
17
|
+
MAX_REQUEST_BODY_BYTES,
|
|
18
|
+
MAX_RICH_TEXT_LENGTH,
|
|
19
|
+
MAX_SEARCH_LENGTH,
|
|
20
|
+
MAX_SHORT_TEXT_LENGTH,
|
|
21
|
+
MAX_TEXT_FIELD_BYTES,
|
|
22
|
+
MAX_URL_LENGTH,
|
|
23
|
+
MAX_WORKSPACES_FOR_FREE_USERS,
|
|
24
|
+
normalizeWorkspaceContextId,
|
|
25
|
+
PERSONAL_WORKSPACE_SLUG,
|
|
26
|
+
ROOT_WORKSPACE_ID,
|
|
27
|
+
resolveWorkspaceId,
|
|
28
|
+
toWorkspaceSlug,
|
|
29
|
+
} from '../constants';
|
|
30
|
+
|
|
31
|
+
describe('Constants', () => {
|
|
32
|
+
describe('ROOT_WORKSPACE_ID', () => {
|
|
33
|
+
it('should be a valid UUID with all zeros', () => {
|
|
34
|
+
expect(ROOT_WORKSPACE_ID).toBe('00000000-0000-0000-0000-000000000000');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should match UUID format', () => {
|
|
38
|
+
const uuidRegex =
|
|
39
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
40
|
+
expect(ROOT_WORKSPACE_ID).toMatch(uuidRegex);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('INTERNAL_WORKSPACE_SLUG', () => {
|
|
45
|
+
it('should be "internal"', () => {
|
|
46
|
+
expect(INTERNAL_WORKSPACE_SLUG).toBe('internal');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('PERSONAL_WORKSPACE_SLUG', () => {
|
|
51
|
+
it('should be "personal"', () => {
|
|
52
|
+
expect(PERSONAL_WORKSPACE_SLUG).toBe('personal');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('MAX_WORKSPACES_FOR_FREE_USERS', () => {
|
|
57
|
+
it('should be a positive number', () => {
|
|
58
|
+
expect(MAX_WORKSPACES_FOR_FREE_USERS).toBeGreaterThan(0);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should be 10', () => {
|
|
62
|
+
expect(MAX_WORKSPACES_FOR_FREE_USERS).toBe(10);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('GitHub constants', () => {
|
|
67
|
+
it('should have correct GitHub owner', () => {
|
|
68
|
+
expect(GITHUB_OWNER).toBe('tutur3u');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should have correct GitHub repo', () => {
|
|
72
|
+
expect(GITHUB_REPO).toBe('platform');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('resolveWorkspaceId', () => {
|
|
78
|
+
describe('internal workspace resolution', () => {
|
|
79
|
+
it('should resolve "internal" to ROOT_WORKSPACE_ID', () => {
|
|
80
|
+
expect(resolveWorkspaceId('internal')).toBe(ROOT_WORKSPACE_ID);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should resolve "INTERNAL" (uppercase) to ROOT_WORKSPACE_ID', () => {
|
|
84
|
+
expect(resolveWorkspaceId('INTERNAL')).toBe(ROOT_WORKSPACE_ID);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should resolve "Internal" (mixed case) to ROOT_WORKSPACE_ID', () => {
|
|
88
|
+
expect(resolveWorkspaceId('Internal')).toBe(ROOT_WORKSPACE_ID);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should resolve "iNtErNaL" (mixed case) to ROOT_WORKSPACE_ID', () => {
|
|
92
|
+
expect(resolveWorkspaceId('iNtErNaL')).toBe(ROOT_WORKSPACE_ID);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('regular workspace IDs', () => {
|
|
97
|
+
it('should return UUID unchanged', () => {
|
|
98
|
+
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
99
|
+
expect(resolveWorkspaceId(uuid)).toBe(uuid);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should return any non-internal string unchanged', () => {
|
|
103
|
+
expect(resolveWorkspaceId('my-workspace')).toBe('my-workspace');
|
|
104
|
+
expect(resolveWorkspaceId('workspace-123')).toBe('workspace-123');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should return "personal" unchanged', () => {
|
|
108
|
+
expect(resolveWorkspaceId('personal')).toBe('personal');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('edge cases', () => {
|
|
113
|
+
it('should return empty string unchanged', () => {
|
|
114
|
+
expect(resolveWorkspaceId('')).toBe('');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should handle strings that contain "internal" but are not exactly "internal"', () => {
|
|
118
|
+
expect(resolveWorkspaceId('internal-workspace')).toBe(
|
|
119
|
+
'internal-workspace'
|
|
120
|
+
);
|
|
121
|
+
expect(resolveWorkspaceId('my-internal')).toBe('my-internal');
|
|
122
|
+
expect(resolveWorkspaceId('internals')).toBe('internals');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('toWorkspaceSlug', () => {
|
|
128
|
+
describe('personal workspace', () => {
|
|
129
|
+
it('should return "personal" when personal option is true', () => {
|
|
130
|
+
expect(toWorkspaceSlug('any-id', { personal: true })).toBe(
|
|
131
|
+
PERSONAL_WORKSPACE_SLUG
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should return "personal" regardless of workspaceId when personal is true', () => {
|
|
136
|
+
expect(toWorkspaceSlug(ROOT_WORKSPACE_ID, { personal: true })).toBe(
|
|
137
|
+
PERSONAL_WORKSPACE_SLUG
|
|
138
|
+
);
|
|
139
|
+
expect(
|
|
140
|
+
toWorkspaceSlug('550e8400-e29b-41d4-a716-446655440000', {
|
|
141
|
+
personal: true,
|
|
142
|
+
})
|
|
143
|
+
).toBe(PERSONAL_WORKSPACE_SLUG);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('internal workspace', () => {
|
|
148
|
+
it('should return "internal" for ROOT_WORKSPACE_ID', () => {
|
|
149
|
+
expect(toWorkspaceSlug(ROOT_WORKSPACE_ID)).toBe(INTERNAL_WORKSPACE_SLUG);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should return "internal" for ROOT_WORKSPACE_ID with personal: false', () => {
|
|
153
|
+
expect(toWorkspaceSlug(ROOT_WORKSPACE_ID, { personal: false })).toBe(
|
|
154
|
+
INTERNAL_WORKSPACE_SLUG
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('regular workspaces', () => {
|
|
160
|
+
it('should return the workspace ID unchanged', () => {
|
|
161
|
+
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
162
|
+
expect(toWorkspaceSlug(uuid)).toBe(uuid);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should return workspace ID when no options provided', () => {
|
|
166
|
+
expect(toWorkspaceSlug('my-workspace')).toBe('my-workspace');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should return workspace ID when personal is false', () => {
|
|
170
|
+
expect(toWorkspaceSlug('my-workspace', { personal: false })).toBe(
|
|
171
|
+
'my-workspace'
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('options handling', () => {
|
|
177
|
+
it('should handle empty options object', () => {
|
|
178
|
+
expect(toWorkspaceSlug('workspace-id', {})).toBe('workspace-id');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should handle undefined options', () => {
|
|
182
|
+
expect(toWorkspaceSlug('workspace-id', undefined)).toBe('workspace-id');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('normalizeWorkspaceContextId', () => {
|
|
188
|
+
it('defaults empty values to the personal workspace slug', () => {
|
|
189
|
+
expect(normalizeWorkspaceContextId()).toBe(PERSONAL_WORKSPACE_SLUG);
|
|
190
|
+
expect(normalizeWorkspaceContextId(null)).toBe(PERSONAL_WORKSPACE_SLUG);
|
|
191
|
+
expect(normalizeWorkspaceContextId(' ')).toBe(PERSONAL_WORKSPACE_SLUG);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('keeps personal workspace identifiers canonicalized to the personal slug', () => {
|
|
195
|
+
expect(normalizeWorkspaceContextId('personal')).toBe(
|
|
196
|
+
PERSONAL_WORKSPACE_SLUG
|
|
197
|
+
);
|
|
198
|
+
expect(normalizeWorkspaceContextId(' PERSONAL ')).toBe(
|
|
199
|
+
PERSONAL_WORKSPACE_SLUG
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('resolves the internal slug to the root workspace UUID', () => {
|
|
204
|
+
expect(normalizeWorkspaceContextId('internal')).toBe(ROOT_WORKSPACE_ID);
|
|
205
|
+
expect(normalizeWorkspaceContextId(' INTERNAL ')).toBe(ROOT_WORKSPACE_ID);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('keeps regular workspace UUIDs unchanged after trimming', () => {
|
|
209
|
+
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
210
|
+
expect(normalizeWorkspaceContextId(` ${uuid} `)).toBe(uuid);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('isInternalWorkspaceSlug', () => {
|
|
215
|
+
describe('internal slug detection', () => {
|
|
216
|
+
it('should return true for "internal"', () => {
|
|
217
|
+
expect(isInternalWorkspaceSlug('internal')).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should return true for "INTERNAL" (uppercase)', () => {
|
|
221
|
+
expect(isInternalWorkspaceSlug('INTERNAL')).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should return true for "Internal" (mixed case)', () => {
|
|
225
|
+
expect(isInternalWorkspaceSlug('Internal')).toBe(true);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should return true for "iNtErNaL" (random case)', () => {
|
|
229
|
+
expect(isInternalWorkspaceSlug('iNtErNaL')).toBe(true);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('non-internal identifiers', () => {
|
|
234
|
+
it('should return false for "personal"', () => {
|
|
235
|
+
expect(isInternalWorkspaceSlug('personal')).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should return false for regular workspace IDs', () => {
|
|
239
|
+
expect(
|
|
240
|
+
isInternalWorkspaceSlug('550e8400-e29b-41d4-a716-446655440000')
|
|
241
|
+
).toBe(false);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should return false for ROOT_WORKSPACE_ID', () => {
|
|
245
|
+
expect(isInternalWorkspaceSlug(ROOT_WORKSPACE_ID)).toBe(false);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should return false for strings containing "internal"', () => {
|
|
249
|
+
expect(isInternalWorkspaceSlug('internal-workspace')).toBe(false);
|
|
250
|
+
expect(isInternalWorkspaceSlug('my-internal')).toBe(false);
|
|
251
|
+
expect(isInternalWorkspaceSlug('internals')).toBe(false);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe('edge cases', () => {
|
|
256
|
+
it('should return false for null', () => {
|
|
257
|
+
expect(isInternalWorkspaceSlug(null)).toBe(false);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should return false for undefined', () => {
|
|
261
|
+
expect(isInternalWorkspaceSlug(undefined)).toBe(false);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should return false for empty string', () => {
|
|
265
|
+
expect(isInternalWorkspaceSlug('')).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('Constant relationships', () => {
|
|
271
|
+
it('resolveWorkspaceId and toWorkspaceSlug should be inverse operations for internal', () => {
|
|
272
|
+
const slug = INTERNAL_WORKSPACE_SLUG;
|
|
273
|
+
const id = resolveWorkspaceId(slug);
|
|
274
|
+
const backToSlug = toWorkspaceSlug(id);
|
|
275
|
+
expect(backToSlug).toBe(slug);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('resolveWorkspaceId should return ROOT_WORKSPACE_ID for internal slug', () => {
|
|
279
|
+
expect(resolveWorkspaceId(INTERNAL_WORKSPACE_SLUG)).toBe(ROOT_WORKSPACE_ID);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('toWorkspaceSlug should return INTERNAL_WORKSPACE_SLUG for ROOT_WORKSPACE_ID', () => {
|
|
283
|
+
expect(toWorkspaceSlug(ROOT_WORKSPACE_ID)).toBe(INTERNAL_WORKSPACE_SLUG);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('Payload protection constants', () => {
|
|
288
|
+
describe('Generic tier constants order', () => {
|
|
289
|
+
it('tiers increase in size', () => {
|
|
290
|
+
const tiers = [
|
|
291
|
+
MAX_CODE_LENGTH,
|
|
292
|
+
MAX_OTP_LENGTH,
|
|
293
|
+
MAX_IP_LENGTH,
|
|
294
|
+
MAX_COLOR_LENGTH,
|
|
295
|
+
MAX_DATE_STRING_LENGTH,
|
|
296
|
+
MAX_SHORT_TEXT_LENGTH,
|
|
297
|
+
MAX_ID_LENGTH,
|
|
298
|
+
MAX_NAME_LENGTH,
|
|
299
|
+
MAX_SEARCH_LENGTH,
|
|
300
|
+
MAX_MEDIUM_TEXT_LENGTH,
|
|
301
|
+
MAX_URL_LENGTH,
|
|
302
|
+
MAX_LONG_TEXT_LENGTH,
|
|
303
|
+
MAX_RICH_TEXT_LENGTH,
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
for (let i = 1; i < tiers.length; i++) {
|
|
307
|
+
expect(tiers[i]).toBeGreaterThanOrEqual(tiers[i - 1]!);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe('Payload size relationships', () => {
|
|
313
|
+
it('MAX_REQUEST_BODY_BYTES >= MAX_PAYLOAD_SIZE', () => {
|
|
314
|
+
expect(MAX_REQUEST_BODY_BYTES).toBeGreaterThanOrEqual(MAX_PAYLOAD_SIZE);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('MAX_TEXT_FIELD_BYTES covers worst-case emoji (4 bytes × MAX_LONG_TEXT_LENGTH)', () => {
|
|
318
|
+
expect(MAX_TEXT_FIELD_BYTES).toBeGreaterThanOrEqual(
|
|
319
|
+
MAX_LONG_TEXT_LENGTH * 4
|
|
320
|
+
);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe('All protection constants are positive integers', () => {
|
|
325
|
+
const constants: Record<string, number> = {
|
|
326
|
+
MAX_CODE_LENGTH,
|
|
327
|
+
MAX_OTP_LENGTH,
|
|
328
|
+
MAX_IP_LENGTH,
|
|
329
|
+
MAX_COLOR_LENGTH,
|
|
330
|
+
MAX_DATE_STRING_LENGTH,
|
|
331
|
+
MAX_SHORT_TEXT_LENGTH,
|
|
332
|
+
MAX_ID_LENGTH,
|
|
333
|
+
MAX_NAME_LENGTH,
|
|
334
|
+
MAX_SEARCH_LENGTH,
|
|
335
|
+
MAX_MEDIUM_TEXT_LENGTH,
|
|
336
|
+
MAX_URL_LENGTH,
|
|
337
|
+
MAX_LONG_TEXT_LENGTH,
|
|
338
|
+
MAX_RICH_TEXT_LENGTH,
|
|
339
|
+
MAX_PAYLOAD_SIZE,
|
|
340
|
+
MAX_REQUEST_BODY_BYTES,
|
|
341
|
+
MAX_TEXT_FIELD_BYTES,
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
for (const [name, value] of Object.entries(constants)) {
|
|
345
|
+
it(`${name} is a positive integer`, () => {
|
|
346
|
+
expect(value).toBeGreaterThan(0);
|
|
347
|
+
expect(Number.isInteger(value)).toBe(true);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { generateSalt, hashPassword } from '../crypto';
|
|
3
|
+
|
|
4
|
+
describe('Crypto Utilities', () => {
|
|
5
|
+
describe('generateSalt', () => {
|
|
6
|
+
it('generates a salt string', () => {
|
|
7
|
+
const salt = generateSalt();
|
|
8
|
+
expect(typeof salt).toBe('string');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('generates salt with correct length (20 hex chars from 10 bytes)', () => {
|
|
12
|
+
const salt = generateSalt();
|
|
13
|
+
expect(salt.length).toBe(20);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('generates hexadecimal string', () => {
|
|
17
|
+
const salt = generateSalt();
|
|
18
|
+
expect(salt).toMatch(/^[0-9a-f]+$/);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('generates different salts on each call', () => {
|
|
22
|
+
const salt1 = generateSalt();
|
|
23
|
+
const salt2 = generateSalt();
|
|
24
|
+
expect(salt1).not.toBe(salt2);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('generates unique salts across many calls', () => {
|
|
28
|
+
const salts = new Set<string>();
|
|
29
|
+
for (let i = 0; i < 100; i++) {
|
|
30
|
+
salts.add(generateSalt());
|
|
31
|
+
}
|
|
32
|
+
expect(salts.size).toBe(100);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('hashPassword', () => {
|
|
37
|
+
it('hashes password with salt', async () => {
|
|
38
|
+
const password = 'testPassword123';
|
|
39
|
+
const salt = 'testSalt';
|
|
40
|
+
const hash = await hashPassword(password, salt);
|
|
41
|
+
expect(typeof hash).toBe('string');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('generates consistent hash for same password and salt', async () => {
|
|
45
|
+
const password = 'myPassword';
|
|
46
|
+
const salt = 'mySalt';
|
|
47
|
+
const hash1 = await hashPassword(password, salt);
|
|
48
|
+
const hash2 = await hashPassword(password, salt);
|
|
49
|
+
expect(hash1).toBe(hash2);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('generates different hash for different passwords', async () => {
|
|
53
|
+
const salt = 'sameSalt';
|
|
54
|
+
const hash1 = await hashPassword('password1', salt);
|
|
55
|
+
const hash2 = await hashPassword('password2', salt);
|
|
56
|
+
expect(hash1).not.toBe(hash2);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('generates different hash for different salts', async () => {
|
|
60
|
+
const password = 'samePassword';
|
|
61
|
+
const hash1 = await hashPassword(password, 'salt1');
|
|
62
|
+
const hash2 = await hashPassword(password, 'salt2');
|
|
63
|
+
expect(hash1).not.toBe(hash2);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('generates 64 character hex string (SHA-256)', async () => {
|
|
67
|
+
const hash = await hashPassword('test', 'salt');
|
|
68
|
+
expect(hash.length).toBe(64);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('generates hexadecimal string', async () => {
|
|
72
|
+
const hash = await hashPassword('test', 'salt');
|
|
73
|
+
expect(hash).toMatch(/^[0-9a-f]+$/);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('handles empty password', async () => {
|
|
77
|
+
const hash = await hashPassword('', 'salt');
|
|
78
|
+
expect(typeof hash).toBe('string');
|
|
79
|
+
expect(hash.length).toBe(64);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('handles empty salt', async () => {
|
|
83
|
+
const hash = await hashPassword('password', '');
|
|
84
|
+
expect(typeof hash).toBe('string');
|
|
85
|
+
expect(hash.length).toBe(64);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('handles special characters in password', async () => {
|
|
89
|
+
const hash = await hashPassword('p@$$w0rd!#$%', 'salt');
|
|
90
|
+
expect(typeof hash).toBe('string');
|
|
91
|
+
expect(hash.length).toBe(64);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('handles unicode characters in password', async () => {
|
|
95
|
+
const hash = await hashPassword('密码テスト', 'salt');
|
|
96
|
+
expect(typeof hash).toBe('string');
|
|
97
|
+
expect(hash.length).toBe(64);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('handles very long password', async () => {
|
|
101
|
+
const longPassword = 'a'.repeat(10000);
|
|
102
|
+
const hash = await hashPassword(longPassword, 'salt');
|
|
103
|
+
expect(typeof hash).toBe('string');
|
|
104
|
+
expect(hash.length).toBe(64);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|