@xivdyetools/test-utils 1.0.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 (131) hide show
  1. package/LICENSE +37 -0
  2. package/README.md +144 -0
  3. package/dist/assertions/index.d.ts +7 -0
  4. package/dist/assertions/index.d.ts.map +1 -0
  5. package/dist/assertions/index.js +7 -0
  6. package/dist/assertions/index.js.map +1 -0
  7. package/dist/assertions/response.d.ts +84 -0
  8. package/dist/assertions/response.d.ts.map +1 -0
  9. package/dist/assertions/response.js +125 -0
  10. package/dist/assertions/response.js.map +1 -0
  11. package/dist/auth/context.d.ts +55 -0
  12. package/dist/auth/context.d.ts.map +1 -0
  13. package/dist/auth/context.js +95 -0
  14. package/dist/auth/context.js.map +1 -0
  15. package/dist/auth/headers.d.ts +64 -0
  16. package/dist/auth/headers.d.ts.map +1 -0
  17. package/dist/auth/headers.js +101 -0
  18. package/dist/auth/headers.js.map +1 -0
  19. package/dist/auth/index.d.ts +10 -0
  20. package/dist/auth/index.d.ts.map +1 -0
  21. package/dist/auth/index.js +10 -0
  22. package/dist/auth/index.js.map +1 -0
  23. package/dist/auth/jwt.d.ts +76 -0
  24. package/dist/auth/jwt.d.ts.map +1 -0
  25. package/dist/auth/jwt.js +77 -0
  26. package/dist/auth/jwt.js.map +1 -0
  27. package/dist/auth/signature.d.ts +64 -0
  28. package/dist/auth/signature.d.ts.map +1 -0
  29. package/dist/auth/signature.js +75 -0
  30. package/dist/auth/signature.js.map +1 -0
  31. package/dist/cloudflare/analytics.d.ts +55 -0
  32. package/dist/cloudflare/analytics.d.ts.map +1 -0
  33. package/dist/cloudflare/analytics.js +44 -0
  34. package/dist/cloudflare/analytics.js.map +1 -0
  35. package/dist/cloudflare/d1.d.ts +101 -0
  36. package/dist/cloudflare/d1.d.ts.map +1 -0
  37. package/dist/cloudflare/d1.js +148 -0
  38. package/dist/cloudflare/d1.js.map +1 -0
  39. package/dist/cloudflare/fetcher.d.ts +72 -0
  40. package/dist/cloudflare/fetcher.d.ts.map +1 -0
  41. package/dist/cloudflare/fetcher.js +136 -0
  42. package/dist/cloudflare/fetcher.js.map +1 -0
  43. package/dist/cloudflare/index.d.ts +12 -0
  44. package/dist/cloudflare/index.d.ts.map +1 -0
  45. package/dist/cloudflare/index.js +12 -0
  46. package/dist/cloudflare/index.js.map +1 -0
  47. package/dist/cloudflare/kv.d.ts +85 -0
  48. package/dist/cloudflare/kv.d.ts.map +1 -0
  49. package/dist/cloudflare/kv.js +138 -0
  50. package/dist/cloudflare/kv.js.map +1 -0
  51. package/dist/cloudflare/r2.d.ts +88 -0
  52. package/dist/cloudflare/r2.d.ts.map +1 -0
  53. package/dist/cloudflare/r2.js +127 -0
  54. package/dist/cloudflare/r2.js.map +1 -0
  55. package/dist/constants/index.d.ts +8 -0
  56. package/dist/constants/index.d.ts.map +1 -0
  57. package/dist/constants/index.js +8 -0
  58. package/dist/constants/index.js.map +1 -0
  59. package/dist/constants/pkce.d.ts +89 -0
  60. package/dist/constants/pkce.d.ts.map +1 -0
  61. package/dist/constants/pkce.js +107 -0
  62. package/dist/constants/pkce.js.map +1 -0
  63. package/dist/constants/secrets.d.ts +72 -0
  64. package/dist/constants/secrets.d.ts.map +1 -0
  65. package/dist/constants/secrets.js +73 -0
  66. package/dist/constants/secrets.js.map +1 -0
  67. package/dist/dom/canvas.d.ts +108 -0
  68. package/dist/dom/canvas.d.ts.map +1 -0
  69. package/dist/dom/canvas.js +125 -0
  70. package/dist/dom/canvas.js.map +1 -0
  71. package/dist/dom/fetch.d.ts +69 -0
  72. package/dist/dom/fetch.d.ts.map +1 -0
  73. package/dist/dom/fetch.js +107 -0
  74. package/dist/dom/fetch.js.map +1 -0
  75. package/dist/dom/index.d.ts +12 -0
  76. package/dist/dom/index.d.ts.map +1 -0
  77. package/dist/dom/index.js +12 -0
  78. package/dist/dom/index.js.map +1 -0
  79. package/dist/dom/localStorage.d.ts +80 -0
  80. package/dist/dom/localStorage.d.ts.map +1 -0
  81. package/dist/dom/localStorage.js +124 -0
  82. package/dist/dom/localStorage.js.map +1 -0
  83. package/dist/dom/matchMedia.d.ts +51 -0
  84. package/dist/dom/matchMedia.d.ts.map +1 -0
  85. package/dist/dom/matchMedia.js +120 -0
  86. package/dist/dom/matchMedia.js.map +1 -0
  87. package/dist/dom/resizeObserver.d.ts +68 -0
  88. package/dist/dom/resizeObserver.d.ts.map +1 -0
  89. package/dist/dom/resizeObserver.js +99 -0
  90. package/dist/dom/resizeObserver.js.map +1 -0
  91. package/dist/factories/category.d.ts +74 -0
  92. package/dist/factories/category.d.ts.map +1 -0
  93. package/dist/factories/category.js +99 -0
  94. package/dist/factories/category.js.map +1 -0
  95. package/dist/factories/dye.d.ts +76 -0
  96. package/dist/factories/dye.d.ts.map +1 -0
  97. package/dist/factories/dye.js +211 -0
  98. package/dist/factories/dye.js.map +1 -0
  99. package/dist/factories/index.d.ts +13 -0
  100. package/dist/factories/index.d.ts.map +1 -0
  101. package/dist/factories/index.js +14 -0
  102. package/dist/factories/index.js.map +1 -0
  103. package/dist/factories/preset.d.ts +105 -0
  104. package/dist/factories/preset.d.ts.map +1 -0
  105. package/dist/factories/preset.js +170 -0
  106. package/dist/factories/preset.js.map +1 -0
  107. package/dist/factories/user.d.ts +74 -0
  108. package/dist/factories/user.d.ts.map +1 -0
  109. package/dist/factories/user.js +115 -0
  110. package/dist/factories/user.js.map +1 -0
  111. package/dist/factories/vote.d.ts +55 -0
  112. package/dist/factories/vote.d.ts.map +1 -0
  113. package/dist/factories/vote.js +68 -0
  114. package/dist/factories/vote.js.map +1 -0
  115. package/dist/index.d.ts +17 -0
  116. package/dist/index.d.ts.map +1 -0
  117. package/dist/index.js +24 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/utils/counters.d.ts +35 -0
  120. package/dist/utils/counters.d.ts.map +1 -0
  121. package/dist/utils/counters.js +53 -0
  122. package/dist/utils/counters.js.map +1 -0
  123. package/dist/utils/crypto.d.ts +42 -0
  124. package/dist/utils/crypto.d.ts.map +1 -0
  125. package/dist/utils/crypto.js +72 -0
  126. package/dist/utils/crypto.js.map +1 -0
  127. package/dist/utils/index.d.ts +6 -0
  128. package/dist/utils/index.d.ts.map +1 -0
  129. package/dist/utils/index.js +6 -0
  130. package/dist/utils/index.js.map +1 -0
  131. package/package.json +88 -0
package/LICENSE ADDED
@@ -0,0 +1,37 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Flash Galatine
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.
22
+
23
+ ---
24
+
25
+ FINAL FANTASY XIV © SQUARE ENIX CO., LTD. All Rights Reserved.
26
+
27
+ This project is a fan-made tool and is not affiliated with, endorsed by, or
28
+ sponsored by Square Enix Co., Ltd. FINAL FANTASY is a registered trademark
29
+ of Square Enix Holdings Co., Ltd.
30
+
31
+ Game data, including dye names, colors, and acquisition methods, are property
32
+ of Square Enix Co., Ltd. and are used under fair use for educational and
33
+ informational purposes only.
34
+
35
+ Market board data is provided by Universalis (https://universalis.app/),
36
+ an independent third-party service not affiliated with Square Enix.
37
+
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # @xivdyetools/test-utils
2
+
3
+ Shared testing utilities for the xivdyetools ecosystem. Provides mocks for Cloudflare Workers bindings, authentication helpers, domain object factories, and DOM utilities.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -D @xivdyetools/test-utils
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **Cloudflare Workers Mocks**: D1Database, KVNamespace, R2Bucket, AnalyticsEngineDataset, Fetcher (Service Bindings)
14
+ - **Auth Helpers**: JWT creation/verification, HMAC signatures, auth context factories
15
+ - **Domain Factories**: Preset, Category, Vote, User mock data factories
16
+ - **DOM Utilities**: localStorage mock, Canvas mock, ResizeObserver mock
17
+ - **Assertions**: Response assertion helpers for API testing
18
+
19
+ ## Usage
20
+
21
+ ### Cloudflare Mocks
22
+
23
+ ```typescript
24
+ import { createMockD1Database, createMockKV, createMockFetcher } from '@xivdyetools/test-utils/cloudflare';
25
+
26
+ // D1 Database mock with query tracking
27
+ const db = createMockD1Database();
28
+ db._setupMock((query, bindings) => {
29
+ if (query.includes('SELECT')) return { id: 1, name: 'Test' };
30
+ return null;
31
+ });
32
+
33
+ // Use in your tests
34
+ const env = { DB: db as unknown as D1Database };
35
+
36
+ // Check what queries were executed
37
+ console.log(db._queries); // ['SELECT ...']
38
+ console.log(db._bindings); // [['param1', 'param2']]
39
+
40
+ // Reset between tests
41
+ db._reset();
42
+ ```
43
+
44
+ ### Auth Helpers
45
+
46
+ ```typescript
47
+ import { createTestJWT, createBotSignature, createAuthContext } from '@xivdyetools/test-utils/auth';
48
+
49
+ // Create a valid JWT for testing
50
+ const jwt = await createTestJWT('your-secret', {
51
+ sub: 'user-123',
52
+ username: 'TestUser',
53
+ global_name: 'Test User',
54
+ });
55
+
56
+ // Create HMAC signature for bot auth
57
+ const signature = await createBotSignature(
58
+ timestamp,
59
+ 'user-discord-id',
60
+ 'username',
61
+ 'signing-secret'
62
+ );
63
+
64
+ // Create auth context for middleware testing
65
+ const ctx = createAuthContext({ isModerator: true });
66
+ ```
67
+
68
+ ### Domain Factories
69
+
70
+ ```typescript
71
+ import {
72
+ createMockPreset,
73
+ createMockPresetRow,
74
+ createMockSubmission,
75
+ createMockUser,
76
+ resetCounters,
77
+ } from '@xivdyetools/test-utils/factories';
78
+
79
+ // Create mock domain objects
80
+ const preset = createMockPreset({ name: 'Custom Name' });
81
+ const row = createMockPresetRow({ status: 'pending' });
82
+ const submission = createMockSubmission();
83
+
84
+ // Reset auto-increment counters between tests
85
+ beforeEach(() => resetCounters());
86
+ ```
87
+
88
+ ### DOM Utilities
89
+
90
+ ```typescript
91
+ import { MockLocalStorage, setupCanvasMocks, setupResizeObserverMock } from '@xivdyetools/test-utils/dom';
92
+
93
+ // Mock localStorage
94
+ const storage = new MockLocalStorage();
95
+ global.localStorage = storage;
96
+
97
+ // Setup canvas mocks for chart testing
98
+ setupCanvasMocks();
99
+
100
+ // Setup ResizeObserver mock
101
+ setupResizeObserverMock();
102
+ ```
103
+
104
+ ### Assertions
105
+
106
+ ```typescript
107
+ import { assertJsonResponse } from '@xivdyetools/test-utils/assertions';
108
+
109
+ const response = await app.request('/api/v1/presets');
110
+ const body = await assertJsonResponse<{ presets: Preset[] }>(response, 200);
111
+ expect(body.presets).toHaveLength(1);
112
+ ```
113
+
114
+ ### Constants
115
+
116
+ ```typescript
117
+ import { VALID_CODE_VERIFIER, VALID_CODE_CHALLENGE } from '@xivdyetools/test-utils/constants';
118
+
119
+ // RFC 7636 compliant PKCE test values
120
+ const params = new URLSearchParams({
121
+ code_verifier: VALID_CODE_VERIFIER,
122
+ code_challenge: VALID_CODE_CHALLENGE,
123
+ });
124
+ ```
125
+
126
+ ## Subpath Exports
127
+
128
+ | Import Path | Contents |
129
+ |-------------|----------|
130
+ | `@xivdyetools/test-utils` | All exports |
131
+ | `@xivdyetools/test-utils/cloudflare` | Cloudflare Workers mocks |
132
+ | `@xivdyetools/test-utils/auth` | Authentication helpers |
133
+ | `@xivdyetools/test-utils/factories` | Domain object factories |
134
+ | `@xivdyetools/test-utils/dom` | DOM/browser utilities |
135
+ | `@xivdyetools/test-utils/assertions` | Response assertions |
136
+ | `@xivdyetools/test-utils/constants` | Test constants (PKCE, etc.) |
137
+
138
+ ## TypeScript
139
+
140
+ This package includes full TypeScript support. Cloudflare Workers types are included via `@cloudflare/workers-types`.
141
+
142
+ ## License
143
+
144
+ MIT
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Assertion helpers for testing
3
+ *
4
+ * Provides assertion utilities for testing API responses.
5
+ */
6
+ export * from './response.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/assertions/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,eAAe,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Assertion helpers for testing
3
+ *
4
+ * Provides assertion utilities for testing API responses.
5
+ */
6
+ export * from './response.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/assertions/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,eAAe,CAAC"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Response assertion helpers for API testing
3
+ *
4
+ * Provides utilities for asserting HTTP response status and body.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * const response = await app.request('/api/presets');
9
+ *
10
+ * // Assert status and get typed body
11
+ * const body = await assertJsonResponse<{ presets: Preset[] }>(response, 200);
12
+ * expect(body.presets).toHaveLength(1);
13
+ *
14
+ * // Assert error response
15
+ * const error = await assertErrorResponse(response, 400);
16
+ * expect(error.error).toBe('Validation failed');
17
+ * ```
18
+ */
19
+ /**
20
+ * Assert response has expected status and return JSON body
21
+ *
22
+ * @param response - The Response object
23
+ * @param expectedStatus - Expected HTTP status code
24
+ * @returns The parsed JSON body
25
+ * @throws If status doesn't match
26
+ */
27
+ export declare function assertJsonResponse<T>(response: Response, expectedStatus: number): Promise<T>;
28
+ /**
29
+ * Error response shape
30
+ */
31
+ export interface ErrorResponse {
32
+ error: string;
33
+ code?: string;
34
+ details?: Record<string, unknown>;
35
+ }
36
+ /**
37
+ * Assert response is an error with expected status
38
+ *
39
+ * @param response - The Response object
40
+ * @param expectedStatus - Expected HTTP status code
41
+ * @returns The parsed error body
42
+ * @throws If status doesn't match
43
+ */
44
+ export declare function assertErrorResponse(response: Response, expectedStatus: number): Promise<ErrorResponse>;
45
+ /**
46
+ * Assert response is OK (2xx status)
47
+ *
48
+ * @param response - The Response object
49
+ * @returns The parsed JSON body
50
+ * @throws If status is not 2xx
51
+ */
52
+ export declare function assertOkResponse<T>(response: Response): Promise<T>;
53
+ /**
54
+ * Assert response is a redirect
55
+ *
56
+ * @param response - The Response object
57
+ * @param expectedLocation - Optional expected Location header value
58
+ * @returns The Location header value
59
+ * @throws If not a redirect or location doesn't match
60
+ */
61
+ export declare function assertRedirectResponse(response: Response, expectedLocation?: string): string;
62
+ /**
63
+ * Assert response has specific headers
64
+ *
65
+ * @param response - The Response object
66
+ * @param expectedHeaders - Headers to check
67
+ * @throws If any header is missing or doesn't match
68
+ */
69
+ export declare function assertHeaders(response: Response, expectedHeaders: Record<string, string>): void;
70
+ /**
71
+ * Assert response has CORS headers
72
+ *
73
+ * @param response - The Response object
74
+ * @param origin - Expected allowed origin
75
+ */
76
+ export declare function assertCorsHeaders(response: Response, origin?: string): void;
77
+ /**
78
+ * Assert response is JSON content type
79
+ *
80
+ * @param response - The Response object
81
+ * @throws If Content-Type is not application/json
82
+ */
83
+ export declare function assertJsonContentType(response: Response): void;
84
+ //# sourceMappingURL=response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/assertions/response.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EACxC,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,CAAC,CAAC,CASZ;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,aAAa,CAAC,CAExB;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CASxE;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,QAAQ,EAClB,gBAAgB,CAAC,EAAE,MAAM,GACxB,MAAM,CAsBR;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,QAAQ,EAClB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACtC,IAAI,CASN;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAY3E;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAQ9D"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Response assertion helpers for API testing
3
+ *
4
+ * Provides utilities for asserting HTTP response status and body.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * const response = await app.request('/api/presets');
9
+ *
10
+ * // Assert status and get typed body
11
+ * const body = await assertJsonResponse<{ presets: Preset[] }>(response, 200);
12
+ * expect(body.presets).toHaveLength(1);
13
+ *
14
+ * // Assert error response
15
+ * const error = await assertErrorResponse(response, 400);
16
+ * expect(error.error).toBe('Validation failed');
17
+ * ```
18
+ */
19
+ // Note: We use a dynamic import approach to avoid hard dependency on vitest
20
+ // Tests should import { expect } from 'vitest' separately
21
+ /**
22
+ * Assert response has expected status and return JSON body
23
+ *
24
+ * @param response - The Response object
25
+ * @param expectedStatus - Expected HTTP status code
26
+ * @returns The parsed JSON body
27
+ * @throws If status doesn't match
28
+ */
29
+ export async function assertJsonResponse(response, expectedStatus) {
30
+ if (response.status !== expectedStatus) {
31
+ const body = await response.text();
32
+ throw new Error(`Expected status ${expectedStatus}, got ${response.status}. Body: ${body}`);
33
+ }
34
+ return response.json();
35
+ }
36
+ /**
37
+ * Assert response is an error with expected status
38
+ *
39
+ * @param response - The Response object
40
+ * @param expectedStatus - Expected HTTP status code
41
+ * @returns The parsed error body
42
+ * @throws If status doesn't match
43
+ */
44
+ export async function assertErrorResponse(response, expectedStatus) {
45
+ return assertJsonResponse(response, expectedStatus);
46
+ }
47
+ /**
48
+ * Assert response is OK (2xx status)
49
+ *
50
+ * @param response - The Response object
51
+ * @returns The parsed JSON body
52
+ * @throws If status is not 2xx
53
+ */
54
+ export async function assertOkResponse(response) {
55
+ if (!response.ok) {
56
+ const body = await response.text();
57
+ throw new Error(`Expected OK response, got ${response.status}. Body: ${body}`);
58
+ }
59
+ return response.json();
60
+ }
61
+ /**
62
+ * Assert response is a redirect
63
+ *
64
+ * @param response - The Response object
65
+ * @param expectedLocation - Optional expected Location header value
66
+ * @returns The Location header value
67
+ * @throws If not a redirect or location doesn't match
68
+ */
69
+ export function assertRedirectResponse(response, expectedLocation) {
70
+ const redirectStatuses = [301, 302, 303, 307, 308];
71
+ if (!redirectStatuses.includes(response.status)) {
72
+ throw new Error(`Expected redirect status (3xx), got ${response.status}`);
73
+ }
74
+ const location = response.headers.get('Location');
75
+ if (!location) {
76
+ throw new Error('Expected Location header in redirect response');
77
+ }
78
+ if (expectedLocation && location !== expectedLocation) {
79
+ throw new Error(`Expected location "${expectedLocation}", got "${location}"`);
80
+ }
81
+ return location;
82
+ }
83
+ /**
84
+ * Assert response has specific headers
85
+ *
86
+ * @param response - The Response object
87
+ * @param expectedHeaders - Headers to check
88
+ * @throws If any header is missing or doesn't match
89
+ */
90
+ export function assertHeaders(response, expectedHeaders) {
91
+ for (const [key, value] of Object.entries(expectedHeaders)) {
92
+ const actual = response.headers.get(key);
93
+ if (actual !== value) {
94
+ throw new Error(`Expected header "${key}" to be "${value}", got "${actual}"`);
95
+ }
96
+ }
97
+ }
98
+ /**
99
+ * Assert response has CORS headers
100
+ *
101
+ * @param response - The Response object
102
+ * @param origin - Expected allowed origin
103
+ */
104
+ export function assertCorsHeaders(response, origin) {
105
+ const acao = response.headers.get('Access-Control-Allow-Origin');
106
+ if (!acao) {
107
+ throw new Error('Expected Access-Control-Allow-Origin header');
108
+ }
109
+ if (origin && acao !== origin && acao !== '*') {
110
+ throw new Error(`Expected CORS origin "${origin}" or "*", got "${acao}"`);
111
+ }
112
+ }
113
+ /**
114
+ * Assert response is JSON content type
115
+ *
116
+ * @param response - The Response object
117
+ * @throws If Content-Type is not application/json
118
+ */
119
+ export function assertJsonContentType(response) {
120
+ const contentType = response.headers.get('Content-Type');
121
+ if (!contentType?.includes('application/json')) {
122
+ throw new Error(`Expected Content-Type application/json, got "${contentType}"`);
123
+ }
124
+ }
125
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/assertions/response.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,4EAA4E;AAC5E,0DAA0D;AAE1D;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,QAAkB,EAClB,cAAsB;IAEtB,IAAI,QAAQ,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CACb,mBAAmB,cAAc,SAAS,QAAQ,CAAC,MAAM,WAAW,IAAI,EAAE,CAC3E,CAAC;IACJ,CAAC;IAED,OAAO,QAAQ,CAAC,IAAI,EAAgB,CAAC;AACvC,CAAC;AAWD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,QAAkB,EAClB,cAAsB;IAEtB,OAAO,kBAAkB,CAAgB,QAAQ,EAAE,cAAc,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAI,QAAkB;IAC1D,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CACb,6BAA6B,QAAQ,CAAC,MAAM,WAAW,IAAI,EAAE,CAC9D,CAAC;IACJ,CAAC;IAED,OAAO,QAAQ,CAAC,IAAI,EAAgB,CAAC;AACvC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,QAAkB,EAClB,gBAAyB;IAEzB,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAEnD,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CACb,uCAAuC,QAAQ,CAAC,MAAM,EAAE,CACzD,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAElD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IAED,IAAI,gBAAgB,IAAI,QAAQ,KAAK,gBAAgB,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CACb,sBAAsB,gBAAgB,WAAW,QAAQ,GAAG,CAC7D,CAAC;IACJ,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAC3B,QAAkB,EAClB,eAAuC;IAEvC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;QAC3D,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CACb,oBAAoB,GAAG,YAAY,KAAK,WAAW,MAAM,GAAG,CAC7D,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAkB,EAAE,MAAe;IACnE,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IAEjE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACjE,CAAC;IAED,IAAI,MAAM,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CACb,yBAAyB,MAAM,kBAAkB,IAAI,GAAG,CACzD,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAkB;IACtD,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEzD,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CACb,gDAAgD,WAAW,GAAG,CAC/D,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Auth context factories for testing middleware and handlers
3
+ *
4
+ * Provides factory functions to create authentication contexts
5
+ * for testing protected endpoints.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // Regular authenticated user
10
+ * const userCtx = createAuthContext({ userDiscordId: '123456789' });
11
+ *
12
+ * // Moderator context
13
+ * const modCtx = createModeratorContext();
14
+ *
15
+ * // Unauthenticated context
16
+ * const anonCtx = createUnauthenticatedContext();
17
+ * ```
18
+ */
19
+ import type { AuthContext, AuthSource } from '@xivdyetools/types/auth';
20
+ export type { AuthContext, AuthSource };
21
+ /**
22
+ * Creates an authenticated user context
23
+ *
24
+ * @param overrides - Optional overrides for the default values
25
+ * @returns An AuthContext for an authenticated user
26
+ */
27
+ export declare function createAuthContext(overrides?: Partial<AuthContext>): AuthContext;
28
+ /**
29
+ * Creates a moderator context
30
+ *
31
+ * @param overrides - Optional overrides for the default values
32
+ * @returns An AuthContext for a moderator
33
+ */
34
+ export declare function createModeratorContext(overrides?: Partial<AuthContext>): AuthContext;
35
+ /**
36
+ * Creates an unauthenticated context
37
+ *
38
+ * @returns An AuthContext for an unauthenticated request
39
+ */
40
+ export declare function createUnauthenticatedContext(): AuthContext;
41
+ /**
42
+ * Creates a web-authenticated context (JWT auth)
43
+ *
44
+ * @param overrides - Optional overrides for the default values
45
+ * @returns An AuthContext for a web-authenticated user
46
+ */
47
+ export declare function createWebAuthContext(overrides?: Partial<AuthContext>): AuthContext;
48
+ /**
49
+ * Creates a bot-authenticated context
50
+ *
51
+ * @param overrides - Optional overrides for the default values
52
+ * @returns An AuthContext for a bot-authenticated user
53
+ */
54
+ export declare function createBotAuthContext(overrides?: Partial<AuthContext>): AuthContext;
55
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/auth/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAGvE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;AAExC;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CASnF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CASxF;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,IAAI,WAAW,CAM1D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAStF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAStF"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Auth context factories for testing middleware and handlers
3
+ *
4
+ * Provides factory functions to create authentication contexts
5
+ * for testing protected endpoints.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // Regular authenticated user
10
+ * const userCtx = createAuthContext({ userDiscordId: '123456789' });
11
+ *
12
+ * // Moderator context
13
+ * const modCtx = createModeratorContext();
14
+ *
15
+ * // Unauthenticated context
16
+ * const anonCtx = createUnauthenticatedContext();
17
+ * ```
18
+ */
19
+ /**
20
+ * Creates an authenticated user context
21
+ *
22
+ * @param overrides - Optional overrides for the default values
23
+ * @returns An AuthContext for an authenticated user
24
+ */
25
+ export function createAuthContext(overrides = {}) {
26
+ return {
27
+ isAuthenticated: true,
28
+ isModerator: false,
29
+ userDiscordId: '123456789',
30
+ userName: 'TestUser',
31
+ authSource: 'bot',
32
+ ...overrides,
33
+ };
34
+ }
35
+ /**
36
+ * Creates a moderator context
37
+ *
38
+ * @param overrides - Optional overrides for the default values
39
+ * @returns An AuthContext for a moderator
40
+ */
41
+ export function createModeratorContext(overrides = {}) {
42
+ return {
43
+ isAuthenticated: true,
44
+ isModerator: true,
45
+ userDiscordId: '123456789',
46
+ userName: 'ModeratorUser',
47
+ authSource: 'bot',
48
+ ...overrides,
49
+ };
50
+ }
51
+ /**
52
+ * Creates an unauthenticated context
53
+ *
54
+ * @returns An AuthContext for an unauthenticated request
55
+ */
56
+ export function createUnauthenticatedContext() {
57
+ return {
58
+ isAuthenticated: false,
59
+ isModerator: false,
60
+ authSource: 'none',
61
+ };
62
+ }
63
+ /**
64
+ * Creates a web-authenticated context (JWT auth)
65
+ *
66
+ * @param overrides - Optional overrides for the default values
67
+ * @returns An AuthContext for a web-authenticated user
68
+ */
69
+ export function createWebAuthContext(overrides = {}) {
70
+ return {
71
+ isAuthenticated: true,
72
+ isModerator: false,
73
+ userDiscordId: '123456789',
74
+ userName: 'WebUser',
75
+ authSource: 'web',
76
+ ...overrides,
77
+ };
78
+ }
79
+ /**
80
+ * Creates a bot-authenticated context
81
+ *
82
+ * @param overrides - Optional overrides for the default values
83
+ * @returns An AuthContext for a bot-authenticated user
84
+ */
85
+ export function createBotAuthContext(overrides = {}) {
86
+ return {
87
+ isAuthenticated: true,
88
+ isModerator: false,
89
+ userDiscordId: '123456789',
90
+ userName: 'BotUser',
91
+ authSource: 'bot',
92
+ ...overrides,
93
+ };
94
+ }
95
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/auth/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAOH;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,YAAkC,EAAE;IACpE,OAAO;QACL,eAAe,EAAE,IAAI;QACrB,WAAW,EAAE,KAAK;QAClB,aAAa,EAAE,WAAW;QAC1B,QAAQ,EAAE,UAAU;QACpB,UAAU,EAAE,KAAK;QACjB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,YAAkC,EAAE;IACzE,OAAO;QACL,eAAe,EAAE,IAAI;QACrB,WAAW,EAAE,IAAI;QACjB,aAAa,EAAE,WAAW;QAC1B,QAAQ,EAAE,eAAe;QACzB,UAAU,EAAE,KAAK;QACjB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,4BAA4B;IAC1C,OAAO;QACL,eAAe,EAAE,KAAK;QACtB,WAAW,EAAE,KAAK;QAClB,UAAU,EAAE,MAAM;KACnB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,YAAkC,EAAE;IACvE,OAAO;QACL,eAAe,EAAE,IAAI;QACrB,WAAW,EAAE,KAAK;QAClB,aAAa,EAAE,WAAW;QAC1B,QAAQ,EAAE,SAAS;QACnB,UAAU,EAAE,KAAK;QACjB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,YAAkC,EAAE;IACvE,OAAO;QACL,eAAe,EAAE,IAAI;QACrB,WAAW,EAAE,KAAK;QAClB,aAAa,EAAE,WAAW;QAC1B,QAAQ,EAAE,SAAS;QACnB,UAAU,EAAE,KAAK;QACjB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Request header builders for testing API authentication
3
+ *
4
+ * Provides helper functions to build authentication headers
5
+ * for testing API endpoints.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // JWT auth headers
10
+ * const headers = authHeaders(jwt);
11
+ *
12
+ * // Bot auth headers with signature
13
+ * const botHeaders = await authHeadersWithSignature(
14
+ * 'bot-token',
15
+ * 'user-id',
16
+ * 'username'
17
+ * );
18
+ *
19
+ * // Use in requests
20
+ * const response = await fetch('/api/protected', { headers });
21
+ * ```
22
+ */
23
+ /**
24
+ * Creates basic auth headers with a bearer token
25
+ *
26
+ * @param token - The bearer token (JWT or API key)
27
+ * @param userId - Optional Discord user ID
28
+ * @param userName - Optional Discord username
29
+ * @returns Headers object
30
+ */
31
+ export declare function authHeaders(token: string, userId?: string, userName?: string): Record<string, string>;
32
+ /**
33
+ * Creates auth headers with HMAC signature for bot authentication
34
+ *
35
+ * @param token - The bot API token
36
+ * @param userId - Discord user ID (optional)
37
+ * @param userName - Discord username (optional)
38
+ * @param signingSecret - The signing secret (defaults to TEST_SIGNING_SECRET)
39
+ * @returns Headers object with signature
40
+ */
41
+ export declare function authHeadersWithSignature(token: string, userId?: string, userName?: string, signingSecret?: string): Promise<Record<string, string>>;
42
+ /**
43
+ * Creates JSON content headers
44
+ *
45
+ * @returns Headers object with Content-Type: application/json
46
+ */
47
+ export declare function jsonHeaders(): Record<string, string>;
48
+ /**
49
+ * Merges multiple header objects
50
+ *
51
+ * @param headerObjects - Header objects to merge
52
+ * @returns Merged headers object
53
+ */
54
+ export declare function mergeHeaders(...headerObjects: Record<string, string>[]): Record<string, string>;
55
+ /**
56
+ * Creates authenticated JSON request headers
57
+ *
58
+ * @param token - The bearer token
59
+ * @param userId - Optional Discord user ID
60
+ * @param userName - Optional Discord username
61
+ * @returns Headers object with auth and content-type
62
+ */
63
+ export declare function authenticatedJsonHeaders(token: string, userId?: string, userName?: string): Record<string, string>;
64
+ //# sourceMappingURL=headers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/auth/headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAcxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,EACjB,aAAa,GAAE,MAA4B,GAC1C,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA2BjC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIpD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,GAAG,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GACzC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAExB;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAExB"}