@theihtisham/devtools-with-cloud 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 (150) hide show
  1. package/.env.example +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +73 -0
  4. package/docker-compose.yml +23 -0
  5. package/jest.config.js +7 -0
  6. package/next-env.d.ts +5 -0
  7. package/next.config.mjs +22 -0
  8. package/package.json +82 -0
  9. package/postcss.config.js +6 -0
  10. package/prisma/schema.prisma +105 -0
  11. package/prisma/seed.ts +211 -0
  12. package/src/app/(app)/ai/page.tsx +122 -0
  13. package/src/app/(app)/collections/page.tsx +155 -0
  14. package/src/app/(app)/environments/page.tsx +96 -0
  15. package/src/app/(app)/history/page.tsx +107 -0
  16. package/src/app/(app)/import/page.tsx +102 -0
  17. package/src/app/(app)/layout.tsx +60 -0
  18. package/src/app/(app)/settings/page.tsx +79 -0
  19. package/src/app/(app)/workspace/page.tsx +284 -0
  20. package/src/app/api/ai/discover/route.ts +17 -0
  21. package/src/app/api/ai/explain/route.ts +29 -0
  22. package/src/app/api/ai/generate-tests/route.ts +37 -0
  23. package/src/app/api/ai/suggest/route.ts +29 -0
  24. package/src/app/api/collections/[id]/route.ts +66 -0
  25. package/src/app/api/collections/route.ts +48 -0
  26. package/src/app/api/environments/route.ts +40 -0
  27. package/src/app/api/export/openapi/route.ts +17 -0
  28. package/src/app/api/export/postman/route.ts +18 -0
  29. package/src/app/api/import/curl/route.ts +18 -0
  30. package/src/app/api/import/har/route.ts +20 -0
  31. package/src/app/api/import/openapi/route.ts +21 -0
  32. package/src/app/api/import/postman/route.ts +21 -0
  33. package/src/app/api/proxy/route.ts +35 -0
  34. package/src/app/api/requests/[id]/execute/route.ts +85 -0
  35. package/src/app/api/requests/[id]/history/route.ts +23 -0
  36. package/src/app/api/requests/[id]/route.ts +66 -0
  37. package/src/app/api/requests/route.ts +49 -0
  38. package/src/app/api/workspaces/route.ts +38 -0
  39. package/src/app/globals.css +99 -0
  40. package/src/app/layout.tsx +24 -0
  41. package/src/app/page.tsx +182 -0
  42. package/src/components/ai/ai-panel.tsx +65 -0
  43. package/src/components/ai/code-explainer.tsx +51 -0
  44. package/src/components/ai/endpoint-discovery.tsx +62 -0
  45. package/src/components/ai/test-generator.tsx +49 -0
  46. package/src/components/collections/collection-actions.tsx +36 -0
  47. package/src/components/collections/collection-tree.tsx +55 -0
  48. package/src/components/collections/folder-creator.tsx +54 -0
  49. package/src/components/landing/comparison.tsx +43 -0
  50. package/src/components/landing/cta.tsx +16 -0
  51. package/src/components/landing/features.tsx +24 -0
  52. package/src/components/landing/hero.tsx +23 -0
  53. package/src/components/response/body-viewer.tsx +33 -0
  54. package/src/components/response/headers-viewer.tsx +23 -0
  55. package/src/components/response/status-badge.tsx +25 -0
  56. package/src/components/response/test-results.tsx +50 -0
  57. package/src/components/response/timing-chart.tsx +39 -0
  58. package/src/components/ui/badge.tsx +24 -0
  59. package/src/components/ui/button.tsx +32 -0
  60. package/src/components/ui/code-editor.tsx +51 -0
  61. package/src/components/ui/dialog.tsx +56 -0
  62. package/src/components/ui/dropdown.tsx +63 -0
  63. package/src/components/ui/input.tsx +22 -0
  64. package/src/components/ui/key-value-editor.tsx +75 -0
  65. package/src/components/ui/select.tsx +24 -0
  66. package/src/components/ui/tabs.tsx +85 -0
  67. package/src/components/ui/textarea.tsx +22 -0
  68. package/src/components/ui/toast.tsx +54 -0
  69. package/src/components/workspace/request-panel.tsx +38 -0
  70. package/src/components/workspace/response-panel.tsx +81 -0
  71. package/src/components/workspace/sidebar.tsx +52 -0
  72. package/src/components/workspace/split-pane.tsx +49 -0
  73. package/src/components/workspace/tabs/auth-tab.tsx +94 -0
  74. package/src/components/workspace/tabs/body-tab.tsx +41 -0
  75. package/src/components/workspace/tabs/headers-tab.tsx +23 -0
  76. package/src/components/workspace/tabs/params-tab.tsx +23 -0
  77. package/src/components/workspace/tabs/pre-request-tab.tsx +26 -0
  78. package/src/components/workspace/url-bar.tsx +53 -0
  79. package/src/hooks/use-ai.ts +115 -0
  80. package/src/hooks/use-collection.ts +71 -0
  81. package/src/hooks/use-environment.ts +73 -0
  82. package/src/hooks/use-request.ts +111 -0
  83. package/src/lib/ai/endpoint-discovery.ts +158 -0
  84. package/src/lib/ai/explainer.ts +127 -0
  85. package/src/lib/ai/suggester.ts +164 -0
  86. package/src/lib/ai/test-generator.ts +161 -0
  87. package/src/lib/auth/api-key.ts +28 -0
  88. package/src/lib/auth/aws-sig.ts +131 -0
  89. package/src/lib/auth/basic.ts +17 -0
  90. package/src/lib/auth/bearer.ts +15 -0
  91. package/src/lib/auth/oauth2.ts +155 -0
  92. package/src/lib/auth/types.ts +16 -0
  93. package/src/lib/db/client.ts +15 -0
  94. package/src/lib/env/manager.ts +32 -0
  95. package/src/lib/env/resolver.ts +30 -0
  96. package/src/lib/exporters/openapi.ts +193 -0
  97. package/src/lib/exporters/postman.ts +140 -0
  98. package/src/lib/graphql/builder.ts +249 -0
  99. package/src/lib/graphql/formatter.ts +147 -0
  100. package/src/lib/graphql/index.ts +43 -0
  101. package/src/lib/graphql/introspection.ts +175 -0
  102. package/src/lib/graphql/types.ts +99 -0
  103. package/src/lib/graphql/validator.ts +216 -0
  104. package/src/lib/http/client.ts +112 -0
  105. package/src/lib/http/proxy.ts +83 -0
  106. package/src/lib/http/request-builder.ts +214 -0
  107. package/src/lib/http/response-parser.ts +106 -0
  108. package/src/lib/http/timing.ts +63 -0
  109. package/src/lib/importers/curl-parser.ts +346 -0
  110. package/src/lib/importers/har-parser.ts +128 -0
  111. package/src/lib/importers/openapi.ts +324 -0
  112. package/src/lib/importers/postman.ts +312 -0
  113. package/src/lib/test-runner/assertions.ts +163 -0
  114. package/src/lib/test-runner/reporter.ts +90 -0
  115. package/src/lib/test-runner/runner.ts +69 -0
  116. package/src/lib/utils/api-response.ts +85 -0
  117. package/src/lib/utils/cn.ts +6 -0
  118. package/src/lib/utils/content-type.ts +123 -0
  119. package/src/lib/utils/download.ts +53 -0
  120. package/src/lib/utils/errors.ts +92 -0
  121. package/src/lib/utils/format.ts +142 -0
  122. package/src/lib/utils/syntax-highlight.ts +108 -0
  123. package/src/lib/utils/validation.ts +231 -0
  124. package/src/lib/websocket/client.ts +182 -0
  125. package/src/lib/websocket/frames.ts +96 -0
  126. package/src/lib/websocket/history.ts +121 -0
  127. package/src/lib/websocket/index.ts +25 -0
  128. package/src/lib/websocket/types.ts +57 -0
  129. package/src/types/ai.ts +28 -0
  130. package/src/types/collection.ts +24 -0
  131. package/src/types/environment.ts +16 -0
  132. package/src/types/request.ts +54 -0
  133. package/src/types/response.ts +37 -0
  134. package/tailwind.config.ts +82 -0
  135. package/tests/lib/env/resolver.test.ts +108 -0
  136. package/tests/lib/graphql/builder.test.ts +349 -0
  137. package/tests/lib/graphql/formatter.test.ts +99 -0
  138. package/tests/lib/http/request-builder.test.ts +160 -0
  139. package/tests/lib/http/response-parser.test.ts +150 -0
  140. package/tests/lib/http/timing.test.ts +188 -0
  141. package/tests/lib/importers/curl-parser.test.ts +245 -0
  142. package/tests/lib/test-runner/assertions.test.ts +342 -0
  143. package/tests/lib/utils/cn.test.ts +46 -0
  144. package/tests/lib/utils/content-type.test.ts +175 -0
  145. package/tests/lib/utils/format.test.ts +188 -0
  146. package/tests/lib/utils/validation.test.ts +237 -0
  147. package/tests/lib/websocket/history.test.ts +186 -0
  148. package/tsconfig.json +29 -0
  149. package/tsconfig.tsbuildinfo +1 -0
  150. package/vitest.config.ts +21 -0
@@ -0,0 +1,342 @@
1
+ // @ts-nocheck
2
+ import { describe, it, expect } from 'vitest';
3
+ import {
4
+ assertions,
5
+ runAssertions,
6
+ } from '@/lib/test-runner/assertions';
7
+
8
+ describe('assertions', () => {
9
+ describe('equals', () => {
10
+ it('should return true for equal values', () => {
11
+ expect(assertions.equals(1, 1)).toBe(true);
12
+ expect(assertions.equals('hello', 'hello')).toBe(true);
13
+ expect(assertions.equals(null, null)).toBe(true);
14
+ });
15
+
16
+ it('should return false for unequal values', () => {
17
+ expect(assertions.equals(1, 2)).toBe(false);
18
+ expect(assertions.equals('a', 'b')).toBe(false);
19
+ });
20
+ });
21
+
22
+ describe('notEquals', () => {
23
+ it('should return true for unequal values', () => {
24
+ expect(assertions.notEquals(1, 2)).toBe(true);
25
+ });
26
+
27
+ it('should return false for equal values', () => {
28
+ expect(assertions.notEquals(1, 1)).toBe(false);
29
+ });
30
+ });
31
+
32
+ describe('greaterThan', () => {
33
+ it('should return true when actual > expected', () => {
34
+ expect(assertions.greaterThan(10, 5)).toBe(true);
35
+ });
36
+
37
+ it('should return false when actual <= expected', () => {
38
+ expect(assertions.greaterThan(5, 5)).toBe(false);
39
+ expect(assertions.greaterThan(3, 5)).toBe(false);
40
+ });
41
+ });
42
+
43
+ describe('lessThan', () => {
44
+ it('should return true when actual < expected', () => {
45
+ expect(assertions.lessThan(5, 10)).toBe(true);
46
+ });
47
+
48
+ it('should return false when actual >= expected', () => {
49
+ expect(assertions.lessThan(5, 5)).toBe(false);
50
+ expect(assertions.lessThan(10, 5)).toBe(false);
51
+ });
52
+ });
53
+
54
+ describe('contains', () => {
55
+ it('should return true when substring is found', () => {
56
+ expect(assertions.contains('hello world', 'world')).toBe(true);
57
+ });
58
+
59
+ it('should return false when substring is not found', () => {
60
+ expect(assertions.contains('hello world', 'foo')).toBe(false);
61
+ });
62
+ });
63
+
64
+ describe('matches', () => {
65
+ it('should return true when pattern matches', () => {
66
+ expect(assertions.matches('hello123', /\d+/)).toBe(true);
67
+ });
68
+
69
+ it('should return false when pattern does not match', () => {
70
+ expect(assertions.matches('hello', /^\d+$/)).toBe(false);
71
+ });
72
+ });
73
+
74
+ describe('hasProperty', () => {
75
+ it('should return true for existing property', () => {
76
+ expect(assertions.hasProperty({ name: 'test' }, 'name')).toBe(true);
77
+ });
78
+
79
+ it('should return false for missing property', () => {
80
+ expect(assertions.hasProperty({ name: 'test' }, 'age')).toBe(false);
81
+ });
82
+
83
+ it('should return false for null', () => {
84
+ expect(assertions.hasProperty(null, 'anything')).toBe(false);
85
+ });
86
+
87
+ it('should return false for undefined', () => {
88
+ expect(assertions.hasProperty(undefined, 'anything')).toBe(false);
89
+ });
90
+ });
91
+
92
+ describe('isArray', () => {
93
+ it('should return true for arrays', () => {
94
+ expect(assertions.isArray([1, 2, 3])).toBe(true);
95
+ expect(assertions.isArray([])).toBe(true);
96
+ });
97
+
98
+ it('should return false for non-arrays', () => {
99
+ expect(assertions.isArray({})).toBe(false);
100
+ expect(assertions.isArray('string')).toBe(false);
101
+ expect(assertions.isArray(42)).toBe(false);
102
+ });
103
+ });
104
+
105
+ describe('isObject', () => {
106
+ it('should return true for plain objects', () => {
107
+ expect(assertions.isObject({})).toBe(true);
108
+ expect(assertions.isObject({ key: 'value' })).toBe(true);
109
+ });
110
+
111
+ it('should return false for arrays', () => {
112
+ expect(assertions.isObject([1, 2])).toBe(false);
113
+ });
114
+
115
+ it('should return false for null', () => {
116
+ expect(assertions.isObject(null)).toBe(false);
117
+ });
118
+
119
+ it('should return false for primitives', () => {
120
+ expect(assertions.isObject('string')).toBe(false);
121
+ expect(assertions.isObject(42)).toBe(false);
122
+ });
123
+ });
124
+
125
+ describe('isString', () => {
126
+ it('should return true for strings', () => {
127
+ expect(assertions.isString('hello')).toBe(true);
128
+ });
129
+
130
+ it('should return false for non-strings', () => {
131
+ expect(assertions.isString(42)).toBe(false);
132
+ });
133
+ });
134
+
135
+ describe('isNumber', () => {
136
+ it('should return true for numbers', () => {
137
+ expect(assertions.isNumber(42)).toBe(true);
138
+ });
139
+
140
+ it('should return false for non-numbers', () => {
141
+ expect(assertions.isNumber('42')).toBe(false);
142
+ });
143
+ });
144
+
145
+ describe('isBoolean', () => {
146
+ it('should return true for booleans', () => {
147
+ expect(assertions.isBoolean(true)).toBe(true);
148
+ expect(assertions.isBoolean(false)).toBe(true);
149
+ });
150
+
151
+ it('should return false for non-booleans', () => {
152
+ expect(assertions.isBoolean('true')).toBe(false);
153
+ });
154
+ });
155
+
156
+ describe('hasStatus', () => {
157
+ it('should return true for matching status', () => {
158
+ expect(assertions.hasStatus(200, 200)).toBe(true);
159
+ });
160
+
161
+ it('should return false for non-matching status', () => {
162
+ expect(assertions.hasStatus(200, 404)).toBe(false);
163
+ });
164
+ });
165
+
166
+ describe('hasHeader', () => {
167
+ it('should return true for existing header (case-insensitive)', () => {
168
+ expect(assertions.hasHeader({ 'content-type': 'application/json' }, 'Content-Type')).toBe(true);
169
+ });
170
+
171
+ it('should return false for missing header', () => {
172
+ expect(assertions.hasHeader({ 'content-type': 'application/json' }, 'Authorization')).toBe(false);
173
+ });
174
+ });
175
+
176
+ describe('hasHeaderWithValue', () => {
177
+ it('should return true for matching header value', () => {
178
+ expect(
179
+ assertions.hasHeaderWithValue({ 'content-type': 'application/json' }, 'Content-Type', 'application/json'),
180
+ ).toBe(true);
181
+ });
182
+
183
+ it('should return false for non-matching value', () => {
184
+ expect(
185
+ assertions.hasHeaderWithValue({ 'content-type': 'text/html' }, 'Content-Type', 'application/json'),
186
+ ).toBe(false);
187
+ });
188
+ });
189
+
190
+ describe('jsonPathEquals', () => {
191
+ const data = { user: { name: 'John', scores: [10, 20, 30] } };
192
+
193
+ it('should find nested value', () => {
194
+ expect(assertions.jsonPathEquals(data, 'user.name', 'John')).toBe(true);
195
+ });
196
+
197
+ it('should return false for wrong value', () => {
198
+ expect(assertions.jsonPathEquals(data, 'user.name', 'Jane')).toBe(false);
199
+ });
200
+
201
+ it('should find array element', () => {
202
+ expect(assertions.jsonPathEquals(data, 'user.scores[0]', 10)).toBe(true);
203
+ });
204
+
205
+ it('should return false for missing path', () => {
206
+ expect(assertions.jsonPathEquals(data, 'user.missing', 'anything')).toBe(false);
207
+ });
208
+ });
209
+
210
+ describe('jsonPathExists', () => {
211
+ const data = { a: { b: 1 } };
212
+
213
+ it('should return true for existing path', () => {
214
+ expect(assertions.jsonPathExists(data, 'a.b')).toBe(true);
215
+ });
216
+
217
+ it('should return true for existing path with undefined value', () => {
218
+ expect(assertions.jsonPathExists(data, 'a')).toBe(true);
219
+ });
220
+
221
+ it('should return false for missing path', () => {
222
+ expect(assertions.jsonPathExists(data, 'a.b.c')).toBe(false);
223
+ });
224
+ });
225
+ });
226
+
227
+ describe('runAssertions', () => {
228
+ const mockResponse = {
229
+ status: 200,
230
+ statusText: 'OK',
231
+ headers: { 'content-type': 'application/json' },
232
+ body: '{"message":"hello","count":5}',
233
+ duration: 150,
234
+ };
235
+
236
+ it('should run pm.test assertions', () => {
237
+ const code = `
238
+ pm.test("Status is 200", function() {
239
+ if (pm.response.status !== 200) throw new Error("Wrong status");
240
+ });
241
+ pm.test("Has correct content type", function() {
242
+ if (pm.response.headers["content-type"] !== "application/json") throw new Error("Wrong type");
243
+ });
244
+ `;
245
+ const results = runAssertions(code, mockResponse);
246
+ expect(results).toHaveLength(2);
247
+ expect(results.every((r) => r.passed)).toBe(true);
248
+ });
249
+
250
+ it('should capture failed assertions', () => {
251
+ const code = `
252
+ pm.test("Status check", function() {
253
+ if (pm.response.status !== 404) throw new Error("Expected 404");
254
+ });
255
+ `;
256
+ const results = runAssertions(code, mockResponse);
257
+ expect(results).toHaveLength(1);
258
+ expect(results[0].passed).toBe(false);
259
+ expect(results[0].error).toContain('Expected 404');
260
+ });
261
+
262
+ it('should provide response.json() accessor', () => {
263
+ const code = `
264
+ pm.test("JSON body check", function() {
265
+ var data = pm.response.json();
266
+ if (data.message !== "hello") throw new Error("Wrong message");
267
+ });
268
+ `;
269
+ const results = runAssertions(code, mockResponse);
270
+ expect(results[0].passed).toBe(true);
271
+ });
272
+
273
+ it('should handle pm.expect.to.equal', () => {
274
+ const code = `
275
+ pm.expect(200).to.equal(200);
276
+ pm.expect("hello").to.equal("hello");
277
+ `;
278
+ const results = runAssertions(code, mockResponse);
279
+ expect(results.length).toBeGreaterThanOrEqual(2);
280
+ expect(results.every((r) => r.passed)).toBe(true);
281
+ });
282
+
283
+ it('should handle pm.expect.to.have.status', () => {
284
+ const code = `
285
+ pm.expect(pm.response).to.have.status(200);
286
+ `;
287
+ const results = runAssertions(code, mockResponse);
288
+ expect(results.length).toBeGreaterThanOrEqual(1);
289
+ const statusResult = results.find((r) => r.name.includes('Status'));
290
+ expect(statusResult?.passed).toBe(true);
291
+ });
292
+
293
+ it('should handle pm.expect.to.exist', () => {
294
+ const code = `
295
+ pm.expect("something").to.exist();
296
+ `;
297
+ const results = runAssertions(code, mockResponse);
298
+ expect(results.some((r) => r.name.includes('exist') && r.passed)).toBe(true);
299
+ });
300
+
301
+ it('should handle pm.expect.to.be.an', () => {
302
+ const code = `
303
+ pm.expect([1,2,3]).to.be.an("array");
304
+ pm.expect({key:"val"}).to.be.an("object");
305
+ pm.expect("hello").to.be.an("string");
306
+ pm.expect(42).to.be.an("number");
307
+ `;
308
+ const results = runAssertions(code, mockResponse);
309
+ expect(results.length).toBe(4);
310
+ expect(results.every((r) => r.passed)).toBe(true);
311
+ });
312
+
313
+ it('should handle pm.expect.to.be.below', () => {
314
+ const code = `
315
+ pm.expect(5).to.be.below(10);
316
+ `;
317
+ const results = runAssertions(code, mockResponse);
318
+ const belowResult = results.find((r) => r.name.includes('below'));
319
+ expect(belowResult?.passed).toBe(true);
320
+ });
321
+
322
+ it('should return execution error for invalid code', () => {
323
+ const code = 'throw new Error("syntax error {{{");';
324
+ const results = runAssertions(code, mockResponse);
325
+ expect(results).toHaveLength(1);
326
+ expect(results[0].passed).toBe(false);
327
+ expect(results[0].name).toBe('Test execution');
328
+ });
329
+
330
+ it('should handle empty test code', () => {
331
+ const results = runAssertions('', mockResponse);
332
+ expect(results).toHaveLength(0);
333
+ });
334
+
335
+ it('should include duration for each test', () => {
336
+ const code = `
337
+ pm.test("quick test", function() {});
338
+ `;
339
+ const results = runAssertions(code, mockResponse);
340
+ expect(results[0].duration).toBeTypeOf('number');
341
+ });
342
+ });
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { cn } from '@/lib/utils/cn';
3
+
4
+ describe('cn', () => {
5
+ it('should combine multiple class names', () => {
6
+ expect(cn('foo', 'bar')).toBe('foo bar');
7
+ });
8
+
9
+ it('should handle conditional classes with object syntax', () => {
10
+ expect(cn('foo', { bar: true, baz: false })).toBe('foo bar');
11
+ });
12
+
13
+ it('should handle arrays of classes', () => {
14
+ expect(cn(['foo', 'bar'], 'baz')).toBe('foo bar baz');
15
+ });
16
+
17
+ it('should filter out falsy values', () => {
18
+ expect(cn('foo', false, null, undefined, '', 'bar')).toBe('foo bar');
19
+ });
20
+
21
+ it('should merge conflicting Tailwind classes using twMerge', () => {
22
+ expect(cn('px-2 py-1', 'px-4')).toBe('py-1 px-4');
23
+ });
24
+
25
+ it('should merge responsive Tailwind classes', () => {
26
+ expect(cn('text-sm md:text-base', 'md:text-lg')).toBe('text-sm md:text-lg');
27
+ });
28
+
29
+ it('should handle empty input', () => {
30
+ expect(cn()).toBe('');
31
+ });
32
+
33
+ it('should handle single class', () => {
34
+ expect(cn('foo')).toBe('foo');
35
+ });
36
+
37
+ it('should handle nested arrays and objects', () => {
38
+ expect(cn(['foo', { bar: true }], { baz: true })).toBe('foo bar baz');
39
+ });
40
+
41
+ it('should deduplicate identical classes', () => {
42
+ // clsx does not deduplicate, but twMerge resolves conflicts
43
+ const result = cn('px-2', 'px-2');
44
+ expect(result).toBe('px-2');
45
+ });
46
+ });
@@ -0,0 +1,175 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ parseContentType,
4
+ detectContentFromBody,
5
+ getExtensionForMimeType,
6
+ type ContentTypeCategory,
7
+ } from '@/lib/utils/content-type';
8
+
9
+ describe('parseContentType', () => {
10
+ it('should return unknown for null header', () => {
11
+ const result = parseContentType(null);
12
+ expect(result).toEqual({
13
+ category: 'unknown',
14
+ mimeType: 'application/octet-stream',
15
+ charset: null,
16
+ isText: false,
17
+ });
18
+ });
19
+
20
+ it('should parse application/json', () => {
21
+ const result = parseContentType('application/json');
22
+ expect(result.category).toBe('json');
23
+ expect(result.mimeType).toBe('application/json');
24
+ expect(result.isText).toBe(true);
25
+ });
26
+
27
+ it('should parse content type with charset', () => {
28
+ const result = parseContentType('application/json; charset=utf-8');
29
+ expect(result.category).toBe('json');
30
+ expect(result.charset).toBe('utf-8');
31
+ });
32
+
33
+ it('should parse content type with quoted charset', () => {
34
+ const result = parseContentType('text/html; charset="utf-8"');
35
+ expect(result.charset).toBe('utf-8');
36
+ });
37
+
38
+ it('should detect JSON variants', () => {
39
+ expect(parseContentType('application/ld+json').category).toBe('json');
40
+ expect(parseContentType('application/vnd.api+json').category).toBe('json');
41
+ expect(parseContentType('application/hal+json').category).toBe('json');
42
+ expect(parseContentType('application/problem+json').category).toBe('json');
43
+ });
44
+
45
+ it('should detect XML types', () => {
46
+ expect(parseContentType('application/xml').category).toBe('xml');
47
+ expect(parseContentType('text/xml').category).toBe('xml');
48
+ expect(parseContentType('application/soap+xml').category).toBe('xml');
49
+ });
50
+
51
+ it('should detect HTML', () => {
52
+ expect(parseContentType('text/html').category).toBe('html');
53
+ });
54
+
55
+ it('should detect text types', () => {
56
+ expect(parseContentType('text/plain').category).toBe('text');
57
+ expect(parseContentType('text/csv').category).toBe('text');
58
+ expect(parseContentType('text/markdown').category).toBe('text');
59
+ });
60
+
61
+ it('should detect form types', () => {
62
+ expect(parseContentType('application/x-www-form-urlencoded').category).toBe('form');
63
+ expect(parseContentType('multipart/form-data').category).toBe('form');
64
+ });
65
+
66
+ it('should detect image types', () => {
67
+ expect(parseContentType('image/png').category).toBe('image');
68
+ expect(parseContentType('image/jpeg').category).toBe('image');
69
+ expect(parseContentType('image/svg+xml').category).toBe('image');
70
+ });
71
+
72
+ it('should detect binary types', () => {
73
+ expect(parseContentType('application/pdf').category).toBe('binary');
74
+ expect(parseContentType('application/zip').category).toBe('binary');
75
+ expect(parseContentType('application/octet-stream').category).toBe('binary');
76
+ });
77
+
78
+ it('should mark unknown MIME types as binary', () => {
79
+ const result = parseContentType('application/some-unknown-type');
80
+ expect(result.category).toBe('binary');
81
+ });
82
+
83
+ it('should treat unknown text/ subtypes as text', () => {
84
+ const result = parseContentType('text/custom');
85
+ expect(result.isText).toBe(true);
86
+ });
87
+
88
+ it('should be case-insensitive for MIME type', () => {
89
+ const result = parseContentType('Application/JSON');
90
+ expect(result.category).toBe('json');
91
+ expect(result.mimeType).toBe('application/json');
92
+ });
93
+ });
94
+
95
+ describe('detectContentFromBody', () => {
96
+ it('should detect JSON object', () => {
97
+ expect(detectContentFromBody('{"key":"value"}')).toBe('json');
98
+ });
99
+
100
+ it('should detect JSON array', () => {
101
+ expect(detectContentFromBody('[1,2,3]')).toBe('json');
102
+ });
103
+
104
+ it('should not detect invalid JSON starting with { as json', () => {
105
+ expect(detectContentFromBody('{invalid json}')).toBe('text');
106
+ });
107
+
108
+ it('should detect XML with xml declaration', () => {
109
+ expect(detectContentFromBody('<?xml version="1.0"?><root/>')).toBe('xml');
110
+ });
111
+
112
+ it('should detect XML with <xml tag', () => {
113
+ expect(detectContentFromBody('<xml>content</xml>')).toBe('xml');
114
+ });
115
+
116
+ it('should detect RSS feed', () => {
117
+ expect(detectContentFromBody('<rss version="2.0"><channel></channel></rss>')).toBe('xml');
118
+ });
119
+
120
+ it('should detect HTML with DOCTYPE', () => {
121
+ expect(detectContentFromBody('<!DOCTYPE html><html></html>')).toBe('html');
122
+ });
123
+
124
+ it('should detect HTML with <html tag', () => {
125
+ expect(detectContentFromBody('<html><body></body></html>')).toBe('html');
126
+ });
127
+
128
+ it('should detect HTML case-insensitively', () => {
129
+ expect(detectContentFromBody('<HTML><body></body></HTML>')).toBe('html');
130
+ });
131
+
132
+ it('should default to text for unknown content', () => {
133
+ expect(detectContentFromBody('just some plain text')).toBe('text');
134
+ });
135
+
136
+ it('should handle whitespace-prefixed content', () => {
137
+ expect(detectContentFromBody(' {"key":"value"}')).toBe('json');
138
+ });
139
+ });
140
+
141
+ describe('getExtensionForMimeType', () => {
142
+ it('should return json for application/json', () => {
143
+ expect(getExtensionForMimeType('application/json')).toBe('json');
144
+ });
145
+
146
+ it('should return xml for application/xml', () => {
147
+ expect(getExtensionForMimeType('application/xml')).toBe('xml');
148
+ });
149
+
150
+ it('should return html for text/html', () => {
151
+ expect(getExtensionForMimeType('text/html')).toBe('html');
152
+ });
153
+
154
+ it('should return png for image/png', () => {
155
+ expect(getExtensionForMimeType('image/png')).toBe('png');
156
+ });
157
+
158
+ it('should return jpg for image/jpeg', () => {
159
+ expect(getExtensionForMimeType('image/jpeg')).toBe('jpg');
160
+ });
161
+
162
+ it('should return bin for unknown types', () => {
163
+ expect(getExtensionForMimeType('application/something')).toBe('bin');
164
+ });
165
+
166
+ it('should return correct extensions for all common types', () => {
167
+ expect(getExtensionForMimeType('text/plain')).toBe('txt');
168
+ expect(getExtensionForMimeType('text/css')).toBe('css');
169
+ expect(getExtensionForMimeType('application/javascript')).toBe('js');
170
+ expect(getExtensionForMimeType('application/pdf')).toBe('pdf');
171
+ expect(getExtensionForMimeType('application/zip')).toBe('zip');
172
+ expect(getExtensionForMimeType('image/gif')).toBe('gif');
173
+ expect(getExtensionForMimeType('image/svg+xml')).toBe('svg');
174
+ });
175
+ });