@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,150 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ parseResponse,
4
+ extractJsonPath,
5
+ flattenHeaders,
6
+ } from '@/lib/http/response-parser';
7
+
8
+ describe('parseResponse', () => {
9
+ it('should parse JSON response', () => {
10
+ const body = '{"message":"hello"}';
11
+ const result = parseResponse(body, 'application/json');
12
+
13
+ expect(result.category).toBe('json');
14
+ expect(result.json).toEqual({ message: 'hello' });
15
+ expect(result.isBinary).toBe(false);
16
+ expect(result.bodySize).toBeGreaterThan(0);
17
+ expect(result.formatted).toContain('message');
18
+ });
19
+
20
+ it('should parse JSON response with charset', () => {
21
+ const body = '{"key":"value"}';
22
+ const result = parseResponse(body, 'application/json; charset=utf-8');
23
+ expect(result.category).toBe('json');
24
+ });
25
+
26
+ it('should parse XML response', () => {
27
+ const body = '<root><child>text</child></root>';
28
+ const result = parseResponse(body, 'application/xml');
29
+
30
+ expect(result.category).toBe('xml');
31
+ expect(result.isBinary).toBe(false);
32
+ expect(result.formatted).toContain('<child>');
33
+ });
34
+
35
+ it('should parse HTML response', () => {
36
+ const body = '<html><body><p>Hello</p></body></html>';
37
+ const result = parseResponse(body, 'text/html');
38
+
39
+ expect(result.category).toBe('html');
40
+ expect(result.isBinary).toBe(false);
41
+ });
42
+
43
+ it('should handle null content type header', () => {
44
+ const body = '{"data":1}';
45
+ const result = parseResponse(body, null);
46
+
47
+ // Should detect JSON from body
48
+ expect(result.category).toBe('json');
49
+ });
50
+
51
+ it('should detect content from body when content type is unknown', () => {
52
+ const body = '{"detected":true}';
53
+ const result = parseResponse(body, 'application/octet-stream');
54
+ expect(result.category).toBe('json');
55
+ });
56
+
57
+ it('should handle invalid JSON body with json content type', () => {
58
+ const body = 'not valid json';
59
+ const result = parseResponse(body, 'application/json');
60
+
61
+ expect(result.category).toBe('text');
62
+ expect(result.json).toBeNull();
63
+ });
64
+
65
+ it('should mark binary content correctly', () => {
66
+ const body = '\x00\x01\x02';
67
+ const result = parseResponse(body, 'application/pdf');
68
+
69
+ expect(result.isBinary).toBe(true);
70
+ });
71
+
72
+ it('should calculate body size correctly', () => {
73
+ const body = 'hello';
74
+ const result = parseResponse(body, 'text/plain');
75
+ expect(result.bodySize).toBe(5);
76
+ });
77
+
78
+ it('should handle text/plain', () => {
79
+ const body = 'plain text content';
80
+ const result = parseResponse(body, 'text/plain');
81
+ expect(result.category).toBe('text');
82
+ expect(result.formatted).toBe('plain text content');
83
+ });
84
+ });
85
+
86
+ describe('extractJsonPath', () => {
87
+ const data = {
88
+ name: 'test',
89
+ nested: {
90
+ value: 42,
91
+ deep: {
92
+ item: 'found',
93
+ },
94
+ },
95
+ items: [
96
+ { id: 1, name: 'first' },
97
+ { id: 2, name: 'second' },
98
+ ],
99
+ };
100
+
101
+ it('should extract top-level property', () => {
102
+ expect(extractJsonPath(data, 'name')).toBe('test');
103
+ });
104
+
105
+ it('should extract nested property', () => {
106
+ expect(extractJsonPath(data, 'nested.value')).toBe(42);
107
+ });
108
+
109
+ it('should extract deeply nested property', () => {
110
+ expect(extractJsonPath(data, 'nested.deep.item')).toBe('found');
111
+ });
112
+
113
+ it('should extract array element by index', () => {
114
+ expect(extractJsonPath(data, 'items[0].name')).toBe('first');
115
+ expect(extractJsonPath(data, 'items[1].id')).toBe(2);
116
+ });
117
+
118
+ it('should return undefined for missing path', () => {
119
+ expect(extractJsonPath(data, 'nonexistent')).toBeUndefined();
120
+ });
121
+
122
+ it('should return undefined for null input', () => {
123
+ expect(extractJsonPath(null, 'something')).toBeUndefined();
124
+ });
125
+
126
+ it('should return undefined for undefined input', () => {
127
+ expect(extractJsonPath(undefined, 'path')).toBeUndefined();
128
+ });
129
+
130
+ it('should handle non-object primitives', () => {
131
+ expect(extractJsonPath(42, 'something')).toBeUndefined();
132
+ expect(extractJsonPath('string', 'path')).toBeUndefined();
133
+ });
134
+ });
135
+
136
+ describe('flattenHeaders', () => {
137
+ it('should flatten headers to key: value format', () => {
138
+ const headers = { 'Content-Type': 'application/json', Authorization: 'Bearer token' };
139
+ const result = flattenHeaders(headers);
140
+ expect(result).toBe('Content-Type: application/json\nAuthorization: Bearer token');
141
+ });
142
+
143
+ it('should handle empty headers', () => {
144
+ expect(flattenHeaders({})).toBe('');
145
+ });
146
+
147
+ it('should handle single header', () => {
148
+ expect(flattenHeaders({ 'X-Custom': 'value' })).toBe('X-Custom: value');
149
+ });
150
+ });
@@ -0,0 +1,188 @@
1
+ // @ts-nocheck
2
+ import { describe, it, expect } from 'vitest';
3
+ import {
4
+ getTimingBreakdown,
5
+ formatTiming,
6
+ getPerformanceRating,
7
+ type RequestTiming,
8
+ } from '@/lib/http/timing';
9
+
10
+ describe('getTimingBreakdown', () => {
11
+ it('should return all timing phases', () => {
12
+ const timing: RequestTiming = {
13
+ dns: 10,
14
+ tcp: 20,
15
+ tls: 30,
16
+ firstByte: 100,
17
+ total: 200,
18
+ };
19
+ const breakdown = getTimingBreakdown(timing);
20
+ expect(breakdown).toHaveLength(5);
21
+ expect(breakdown[0].label).toBe('DNS Lookup');
22
+ expect(breakdown[1].label).toBe('TCP Connect');
23
+ expect(breakdown[2].label).toBe('TLS Handshake');
24
+ expect(breakdown[3].label).toBe('First Byte (TTFB)');
25
+ expect(breakdown[4].label).toBe('Content Transfer');
26
+ });
27
+
28
+ it('should calculate percentages correctly', () => {
29
+ const timing: RequestTiming = {
30
+ dns: 10,
31
+ tcp: 20,
32
+ tls: 30,
33
+ firstByte: 40,
34
+ total: 100,
35
+ };
36
+ const breakdown = getTimingBreakdown(timing);
37
+ expect(breakdown[0].percentage).toBe(10);
38
+ expect(breakdown[1].percentage).toBe(20);
39
+ expect(breakdown[2].percentage).toBe(30);
40
+ expect(breakdown[3].percentage).toBe(40);
41
+ });
42
+
43
+ it('should calculate content transfer as total - firstByte', () => {
44
+ const timing: RequestTiming = {
45
+ dns: 5,
46
+ tcp: 5,
47
+ tls: 10,
48
+ firstByte: 50,
49
+ total: 100,
50
+ };
51
+ const breakdown = getTimingBreakdown(timing);
52
+ expect(breakdown[4].value).toBe(50);
53
+ expect(breakdown[4].percentage).toBe(50);
54
+ });
55
+
56
+ it('should handle zero total (avoids division by zero)', () => {
57
+ const timing: RequestTiming = {
58
+ dns: 0,
59
+ tcp: 0,
60
+ tls: 0,
61
+ firstByte: 0,
62
+ total: 0,
63
+ };
64
+ const breakdown = getTimingBreakdown(timing);
65
+ expect(breakdown).toHaveLength(5);
66
+ // All percentages should be 0 since all values are 0
67
+ for (const phase of breakdown) {
68
+ expect(isNaN(phase.percentage)).toBe(false);
69
+ }
70
+ });
71
+
72
+ it('should include colors for each phase', () => {
73
+ const timing: RequestTiming = {
74
+ dns: 10,
75
+ tcp: 10,
76
+ tls: 10,
77
+ firstByte: 10,
78
+ total: 50,
79
+ };
80
+ const breakdown = getTimingBreakdown(timing);
81
+ for (const phase of breakdown) {
82
+ expect(phase.color).toBeTruthy();
83
+ expect(phase.color).toMatch(/^#[0-9a-f]{6}$/);
84
+ }
85
+ });
86
+ });
87
+
88
+ describe('formatTiming', () => {
89
+ it('should format timing with all phases', () => {
90
+ const timing: RequestTiming = {
91
+ dns: 10,
92
+ tcp: 20,
93
+ tls: 30,
94
+ firstByte: 100,
95
+ total: 200,
96
+ };
97
+ const result = formatTiming(timing);
98
+ expect(result).toContain('DNS: 10ms');
99
+ expect(result).toContain('TCP: 20ms');
100
+ expect(result).toContain('TLS: 30ms');
101
+ expect(result).toContain('TTFB: 100ms');
102
+ expect(result).toContain('Total: 200ms');
103
+ });
104
+
105
+ it('should skip zero-value phases except TTFB and Total', () => {
106
+ const timing: RequestTiming = {
107
+ dns: 0,
108
+ tcp: 0,
109
+ tls: 0,
110
+ firstByte: 50,
111
+ total: 50,
112
+ };
113
+ const result = formatTiming(timing);
114
+ expect(result).not.toContain('DNS');
115
+ expect(result).not.toContain('TCP');
116
+ expect(result).not.toContain('TLS');
117
+ expect(result).toContain('TTFB: 50ms');
118
+ expect(result).toContain('Total: 50ms');
119
+ });
120
+
121
+ it('should always include TTFB and Total', () => {
122
+ const timing: RequestTiming = {
123
+ dns: 0,
124
+ tcp: 0,
125
+ tls: 0,
126
+ firstByte: 0,
127
+ total: 0,
128
+ };
129
+ const result = formatTiming(timing);
130
+ expect(result).toContain('TTFB: 0ms');
131
+ expect(result).toContain('Total: 0ms');
132
+ });
133
+
134
+ it('should separate phases with pipe', () => {
135
+ const timing: RequestTiming = {
136
+ dns: 5,
137
+ tcp: 5,
138
+ tls: 5,
139
+ firstByte: 10,
140
+ total: 30,
141
+ };
142
+ const result = formatTiming(timing);
143
+ expect(result).toContain(' | ');
144
+ });
145
+ });
146
+
147
+ describe('getPerformanceRating', () => {
148
+ it('should rate fast responses (< 200ms)', () => {
149
+ const result = getPerformanceRating(100);
150
+ expect(result.label).toBe('Fast');
151
+ expect(result.color).toContain('green');
152
+ });
153
+
154
+ it('should rate good responses (< 500ms)', () => {
155
+ const result = getPerformanceRating(300);
156
+ expect(result.label).toBe('Good');
157
+ expect(result.color).toContain('green');
158
+ });
159
+
160
+ it('should rate moderate responses (< 1000ms)', () => {
161
+ const result = getPerformanceRating(750);
162
+ expect(result.label).toBe('Moderate');
163
+ expect(result.color).toContain('yellow');
164
+ });
165
+
166
+ it('should rate slow responses (< 3000ms)', () => {
167
+ const result = getPerformanceRating(1500);
168
+ expect(result.label).toBe('Slow');
169
+ expect(result.color).toContain('orange');
170
+ });
171
+
172
+ it('should rate very slow responses (>= 3000ms)', () => {
173
+ const result = getPerformanceRating(5000);
174
+ expect(result.label).toBe('Very Slow');
175
+ expect(result.color).toContain('red');
176
+ });
177
+
178
+ it('should handle boundary values', () => {
179
+ expect(getPerformanceRating(199).label).toBe('Fast');
180
+ expect(getPerformanceRating(200).label).toBe('Good');
181
+ expect(getPerformanceRating(499).label).toBe('Good');
182
+ expect(getPerformanceRating(500).label).toBe('Moderate');
183
+ expect(getPerformanceRating(999).label).toBe('Moderate');
184
+ expect(getPerformanceRating(1000).label).toBe('Slow');
185
+ expect(getPerformanceRating(2999).label).toBe('Slow');
186
+ expect(getPerformanceRating(3000).label).toBe('Very Slow');
187
+ });
188
+ });
@@ -0,0 +1,245 @@
1
+ // @ts-nocheck
2
+ import { describe, it, expect } from 'vitest';
3
+ import { parseCurl, generateCurl } from '@/lib/importers/curl-parser';
4
+
5
+ describe('parseCurl', () => {
6
+ it('should parse a simple GET request', () => {
7
+ const result = parseCurl('curl https://api.example.com/users');
8
+ expect(result.method).toBe('GET');
9
+ expect(result.url).toBe('https://api.example.com/users');
10
+ });
11
+
12
+ it('should parse a POST request with -X', () => {
13
+ const result = parseCurl('curl -X POST https://api.example.com/users');
14
+ expect(result.method).toBe('POST');
15
+ expect(result.url).toBe('https://api.example.com/users');
16
+ });
17
+
18
+ it('should parse a PUT request with --request', () => {
19
+ const result = parseCurl('curl --request PUT https://api.example.com/users/1');
20
+ expect(result.method).toBe('PUT');
21
+ });
22
+
23
+ it('should parse headers with -H', () => {
24
+ const result = parseCurl('curl -H "Content-Type: application/json" https://api.example.com');
25
+ expect(result.headers).toHaveLength(1);
26
+ expect(result.headers[0].key).toBe('Content-Type');
27
+ expect(result.headers[0].value).toBe('application/json');
28
+ expect(result.headers[0].enabled).toBe(true);
29
+ });
30
+
31
+ it('should parse multiple headers', () => {
32
+ const result = parseCurl(
33
+ 'curl -H "Content-Type: application/json" -H "Authorization: Bearer token" https://api.example.com',
34
+ );
35
+ expect(result.headers).toHaveLength(2);
36
+ });
37
+
38
+ it('should parse data with -d and default to POST', () => {
39
+ const result = parseCurl('curl -d \'{"name":"test"}\' https://api.example.com/users');
40
+ expect(result.method).toBe('POST');
41
+ expect(result.body).toBe('{"name":"test"}');
42
+ expect(result.bodyType).toBe('json');
43
+ });
44
+
45
+ it('should parse data with --data-raw', () => {
46
+ const result = parseCurl('curl --data-raw "hello=world" https://api.example.com');
47
+ expect(result.body).toBe('hello=world');
48
+ expect(result.bodyType).toBe('urlencoded');
49
+ expect(result.method).toBe('POST');
50
+ });
51
+
52
+ it('should detect urlencoded body type', () => {
53
+ const result = parseCurl('curl -d "key=value&foo=bar" https://api.example.com');
54
+ expect(result.bodyType).toBe('urlencoded');
55
+ });
56
+
57
+ it('should detect json body type for object', () => {
58
+ const result = parseCurl('curl -d \'{"key":"value"}\' https://api.example.com');
59
+ expect(result.bodyType).toBe('json');
60
+ });
61
+
62
+ it('should detect json body type for array', () => {
63
+ const result = parseCurl('curl -d \'[1,2,3]\' https://api.example.com');
64
+ expect(result.bodyType).toBe('json');
65
+ });
66
+
67
+ it('should parse form data with -F', () => {
68
+ const result = parseCurl('curl -F "file=@photo.jpg" https://api.example.com/upload');
69
+ expect(result.bodyType).toBe('form');
70
+ expect(result.method).toBe('POST');
71
+ });
72
+
73
+ it('should parse basic auth with -u', () => {
74
+ const result = parseCurl('curl -u user:pass https://api.example.com');
75
+ expect(result.auth).toEqual({
76
+ type: 'basic',
77
+ username: 'user',
78
+ password: 'pass',
79
+ });
80
+ });
81
+
82
+ it('should parse basic auth with username only', () => {
83
+ const result = parseCurl('curl -u user https://api.example.com');
84
+ expect(result.auth).toEqual({
85
+ type: 'basic',
86
+ username: 'user',
87
+ password: '',
88
+ });
89
+ });
90
+
91
+ it('should parse OAuth2 bearer token', () => {
92
+ const result = parseCurl('curl --oauth2-bearer my-token https://api.example.com');
93
+ expect(result.auth).toEqual({
94
+ type: 'bearer',
95
+ token: 'my-token',
96
+ prefix: 'Bearer',
97
+ });
98
+ });
99
+
100
+ it('should parse cookies with -b', () => {
101
+ const result = parseCurl('curl -b "session=abc123" https://api.example.com');
102
+ expect(result.headers).toContainEqual({
103
+ key: 'Cookie',
104
+ value: 'session=abc123',
105
+ enabled: true,
106
+ });
107
+ });
108
+
109
+ it('should parse user agent with -A', () => {
110
+ const result = parseCurl('curl -A "MyApp/1.0" https://api.example.com');
111
+ expect(result.headers).toContainEqual({
112
+ key: 'User-Agent',
113
+ value: 'MyApp/1.0',
114
+ enabled: true,
115
+ });
116
+ });
117
+
118
+ it('should add Accept-Encoding for --compressed', () => {
119
+ const result = parseCurl('curl --compressed https://api.example.com');
120
+ expect(result.headers).toContainEqual({
121
+ key: 'Accept-Encoding',
122
+ value: 'gzip, deflate',
123
+ enabled: true,
124
+ });
125
+ });
126
+
127
+ it('should extract query params from URL', () => {
128
+ const result = parseCurl('curl "https://api.example.com/users?page=1&limit=10"');
129
+ expect(result.params).toHaveLength(2);
130
+ expect(result.params).toContainEqual({ key: 'page', value: '1', enabled: true });
131
+ expect(result.params).toContainEqual({ key: 'limit', value: '10', enabled: true });
132
+ });
133
+
134
+ it('should throw for empty command', () => {
135
+ expect(() => parseCurl('')).toThrow('Empty cURL command');
136
+ });
137
+
138
+ it('should throw when no URL is found', () => {
139
+ expect(() => parseCurl('curl -H "Content-Type: application/json"')).toThrow('No URL found');
140
+ });
141
+
142
+ it('should handle command without curl prefix', () => {
143
+ const result = parseCurl('https://api.example.com');
144
+ // new URL('https://api.example.com') produces origin + pathname = 'https://api.example.com/'
145
+ expect(result.url).toBe('https://api.example.com/');
146
+ expect(result.method).toBe('GET');
147
+ });
148
+
149
+ it('should ignore silent/verbose flags', () => {
150
+ const result = parseCurl('curl -s -v https://api.example.com');
151
+ expect(result.url).toBe('https://api.example.com/');
152
+ });
153
+
154
+ it('should handle single-quoted URL', () => {
155
+ const result = parseCurl("curl 'https://api.example.com/path?q=hello world'");
156
+ expect(result.url).toBeDefined();
157
+ });
158
+
159
+ it('should handle DELETE method', () => {
160
+ const result = parseCurl('curl -X DELETE https://api.example.com/users/1');
161
+ expect(result.method).toBe('DELETE');
162
+ });
163
+
164
+ it('should handle PATCH method', () => {
165
+ const result = parseCurl('curl -X PATCH https://api.example.com/users/1');
166
+ expect(result.method).toBe('PATCH');
167
+ });
168
+ });
169
+
170
+ describe('generateCurl', () => {
171
+ it('should generate a basic GET curl command', () => {
172
+ const result = generateCurl({
173
+ method: 'GET',
174
+ url: 'https://api.example.com/users',
175
+ });
176
+ expect(result).toContain('curl -X GET');
177
+ expect(result).toContain('https://api.example.com/users');
178
+ });
179
+
180
+ it('should include headers', () => {
181
+ const result = generateCurl({
182
+ method: 'GET',
183
+ url: 'https://api.example.com',
184
+ headers: [
185
+ { key: 'Content-Type', value: 'application/json', enabled: true },
186
+ { key: 'Auth', value: 'token', enabled: false },
187
+ ],
188
+ });
189
+ expect(result).toContain("-H 'Content-Type: application/json'");
190
+ expect(result).not.toContain("'Auth: token'");
191
+ });
192
+
193
+ it('should include body data', () => {
194
+ const result = generateCurl({
195
+ method: 'POST',
196
+ url: 'https://api.example.com',
197
+ body: '{"name":"test"}',
198
+ });
199
+ expect(result).toContain("--data-raw '{\"name\":\"test\"}'");
200
+ });
201
+
202
+ it('should quote URLs with spaces', () => {
203
+ const result = generateCurl({
204
+ method: 'GET',
205
+ url: 'https://example.com/path with spaces',
206
+ });
207
+ expect(result).toContain("'https://example.com/path with spaces'");
208
+ });
209
+
210
+ it('should include basic auth', () => {
211
+ const result = generateCurl({
212
+ method: 'GET',
213
+ url: 'https://api.example.com',
214
+ auth: { type: 'basic', username: 'user', password: 'pass' },
215
+ });
216
+ expect(result).toContain("-u 'user:pass'");
217
+ });
218
+
219
+ it('should include bearer auth as header', () => {
220
+ const result = generateCurl({
221
+ method: 'GET',
222
+ url: 'https://api.example.com',
223
+ auth: { type: 'bearer', token: 'my-token' },
224
+ });
225
+ expect(result).toContain("'Authorization: Bearer my-token'");
226
+ });
227
+
228
+ it('should skip headers with empty key', () => {
229
+ const result = generateCurl({
230
+ method: 'GET',
231
+ url: 'https://api.example.com',
232
+ headers: [{ key: '', value: 'test', enabled: true }],
233
+ });
234
+ expect(result).not.toContain("-H ''");
235
+ });
236
+
237
+ it('should escape single quotes in body', () => {
238
+ const result = generateCurl({
239
+ method: 'POST',
240
+ url: 'https://api.example.com',
241
+ body: "it's a test",
242
+ });
243
+ expect(result).toContain("it'\\''s a test");
244
+ });
245
+ });