@gencow/core 0.1.24 → 0.1.26

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 (75) hide show
  1. package/dist/crud.d.ts +2 -2
  2. package/dist/crud.js +225 -208
  3. package/dist/index.d.ts +5 -5
  4. package/dist/index.js +2 -2
  5. package/dist/reactive.js +10 -3
  6. package/dist/retry.js +1 -1
  7. package/dist/rls-db.d.ts +2 -2
  8. package/dist/rls-db.js +1 -5
  9. package/dist/scheduler.d.ts +2 -0
  10. package/dist/scheduler.js +16 -6
  11. package/dist/server.d.ts +0 -1
  12. package/dist/server.js +0 -1
  13. package/dist/storage.js +29 -22
  14. package/dist/v.d.ts +2 -2
  15. package/dist/workflow.js +4 -11
  16. package/dist/workflows-api.js +5 -12
  17. package/package.json +45 -42
  18. package/src/__tests__/auth.test.ts +90 -86
  19. package/src/__tests__/crons.test.ts +69 -67
  20. package/src/__tests__/crud-codegen-integration.test.ts +164 -170
  21. package/src/__tests__/crud-owner-rls.test.ts +308 -301
  22. package/src/__tests__/crud.test.ts +694 -711
  23. package/src/__tests__/dist-exports.test.ts +120 -120
  24. package/src/__tests__/fixtures/basic/auth.ts +16 -16
  25. package/src/__tests__/fixtures/basic/drizzle.config.ts +1 -4
  26. package/src/__tests__/fixtures/basic/index.ts +1 -1
  27. package/src/__tests__/fixtures/basic/schema.ts +1 -1
  28. package/src/__tests__/fixtures/basic/tasks.ts +4 -4
  29. package/src/__tests__/fixtures/common/auth-schema.ts +38 -34
  30. package/src/__tests__/helpers/basic-rls-fixture.ts +80 -78
  31. package/src/__tests__/helpers/pglite-migrations.ts +2 -5
  32. package/src/__tests__/helpers/pglite-rls-session.ts +13 -16
  33. package/src/__tests__/helpers/seed-like-fill.ts +47 -41
  34. package/src/__tests__/helpers/test-gencow-ctx-rls.ts +4 -7
  35. package/src/__tests__/httpaction.test.ts +91 -91
  36. package/src/__tests__/image-optimization.test.ts +570 -574
  37. package/src/__tests__/load.test.ts +321 -308
  38. package/src/__tests__/network-sim.test.ts +238 -215
  39. package/src/__tests__/reactive.test.ts +380 -358
  40. package/src/__tests__/retry.test.ts +99 -84
  41. package/src/__tests__/rls-crud-basic.test.ts +172 -245
  42. package/src/__tests__/rls-crud-no-owner-rls-pglite.test.ts +81 -81
  43. package/src/__tests__/rls-custom-mutation-handlers.test.ts +47 -94
  44. package/src/__tests__/rls-custom-query-handlers.test.ts +92 -92
  45. package/src/__tests__/rls-db-leased-connection.test.ts +2 -6
  46. package/src/__tests__/rls-session-and-policies.test.ts +181 -199
  47. package/src/__tests__/scheduler-durable-v2.test.ts +199 -181
  48. package/src/__tests__/scheduler-durable.test.ts +117 -117
  49. package/src/__tests__/scheduler-exec.test.ts +258 -246
  50. package/src/__tests__/scheduler.test.ts +129 -111
  51. package/src/__tests__/storage.test.ts +282 -269
  52. package/src/__tests__/tsconfig.json +6 -6
  53. package/src/__tests__/validator.test.ts +236 -232
  54. package/src/__tests__/workflow.test.ts +309 -286
  55. package/src/__tests__/ws-integration.test.ts +223 -218
  56. package/src/__tests__/ws-scale.test.ts +168 -159
  57. package/src/auth-config.ts +18 -18
  58. package/src/auth.ts +106 -106
  59. package/src/crons.ts +77 -77
  60. package/src/crud.ts +523 -479
  61. package/src/index.ts +69 -5
  62. package/src/reactive.ts +357 -331
  63. package/src/retry.ts +51 -54
  64. package/src/rls-db.ts +195 -205
  65. package/src/rls.ts +33 -36
  66. package/src/scheduler.ts +237 -211
  67. package/src/server.ts +0 -1
  68. package/src/storage.ts +632 -593
  69. package/src/v.ts +119 -114
  70. package/src/workflow-types.ts +67 -70
  71. package/src/workflow.ts +99 -116
  72. package/src/workflows-api.ts +231 -241
  73. package/dist/db.d.ts +0 -13
  74. package/dist/db.js +0 -16
  75. package/src/db.ts +0 -18
@@ -7,313 +7,317 @@
7
7
  */
8
8
 
9
9
  import { describe, it, expect } from "bun:test";
10
- import { v, parseArgs, GencowValidationError } from "../v";
10
+ import { v, parseArgs, GencowValidationError } from "../v.js";
11
11
 
12
12
  // ─── v.string() ─────────────────────────────────────────
13
13
 
14
14
  describe("v.string()", () => {
15
- const validator = v.string();
15
+ const validator = v.string();
16
16
 
17
- it("문자열 통과", () => {
18
- expect(validator.parse("hello")).toBe("hello");
19
- });
17
+ it("문자열 통과", () => {
18
+ expect(validator.parse("hello")).toBe("hello");
19
+ });
20
20
 
21
- it("빈 문자열 통과", () => {
22
- expect(validator.parse("")).toBe("");
23
- });
21
+ it("빈 문자열 통과", () => {
22
+ expect(validator.parse("")).toBe("");
23
+ });
24
24
 
25
- it("숫자 → 에러", () => {
26
- expect(() => validator.parse(42)).toThrow("Expected string");
27
- });
25
+ it("숫자 → 에러", () => {
26
+ expect(() => validator.parse(42)).toThrow("Expected string");
27
+ });
28
28
 
29
- it("불리언 → 에러", () => {
30
- expect(() => validator.parse(true)).toThrow("Expected string");
31
- });
29
+ it("불리언 → 에러", () => {
30
+ expect(() => validator.parse(true)).toThrow("Expected string");
31
+ });
32
32
 
33
- it("null → 에러", () => {
34
- expect(() => validator.parse(null)).toThrow("Expected string");
35
- });
33
+ it("null → 에러", () => {
34
+ expect(() => validator.parse(null)).toThrow("Expected string");
35
+ });
36
36
 
37
- it("undefined → 에러", () => {
38
- expect(() => validator.parse(undefined)).toThrow("Expected string");
39
- });
37
+ it("undefined → 에러", () => {
38
+ expect(() => validator.parse(undefined)).toThrow("Expected string");
39
+ });
40
40
  });
41
41
 
42
42
  // ─── v.number() ─────────────────────────────────────────
43
43
 
44
44
  describe("v.number()", () => {
45
- const validator = v.number();
45
+ const validator = v.number();
46
46
 
47
- it("정수 통과", () => {
48
- expect(validator.parse(42)).toBe(42);
49
- });
47
+ it("정수 통과", () => {
48
+ expect(validator.parse(42)).toBe(42);
49
+ });
50
50
 
51
- it("실수 통과", () => {
52
- expect(validator.parse(3.14)).toBe(3.14);
53
- });
51
+ it("실수 통과", () => {
52
+ expect(validator.parse(3.14)).toBe(3.14);
53
+ });
54
54
 
55
- it("0 통과", () => {
56
- expect(validator.parse(0)).toBe(0);
57
- });
55
+ it("0 통과", () => {
56
+ expect(validator.parse(0)).toBe(0);
57
+ });
58
58
 
59
- it("음수 통과", () => {
60
- expect(validator.parse(-10)).toBe(-10);
61
- });
59
+ it("음수 통과", () => {
60
+ expect(validator.parse(-10)).toBe(-10);
61
+ });
62
62
 
63
- it("숫자 문자열 → 자동 변환", () => {
64
- expect(validator.parse("42")).toBe(42);
65
- });
63
+ it("숫자 문자열 → 자동 변환", () => {
64
+ expect(validator.parse("42")).toBe(42);
65
+ });
66
66
 
67
- it("비숫자 문자열 → 에러", () => {
68
- expect(() => validator.parse("abc")).toThrow("Expected number");
69
- });
67
+ it("비숫자 문자열 → 에러", () => {
68
+ expect(() => validator.parse("abc")).toThrow("Expected number");
69
+ });
70
70
 
71
- it("불리언 → 에러", () => {
72
- expect(() => validator.parse(true)).toThrow("Expected number");
73
- });
71
+ it("불리언 → 에러", () => {
72
+ expect(() => validator.parse(true)).toThrow("Expected number");
73
+ });
74
74
 
75
- it("NaN 입력 → 에러", () => {
76
- expect(() => validator.parse(NaN)).toThrow("Expected number");
77
- });
75
+ it("NaN 입력 → 에러", () => {
76
+ expect(() => validator.parse(NaN)).toThrow("Expected number");
77
+ });
78
78
  });
79
79
 
80
80
  // ─── v.boolean() ────────────────────────────────────────
81
81
 
82
82
  describe("v.boolean()", () => {
83
- const validator = v.boolean();
83
+ const validator = v.boolean();
84
84
 
85
- it("true 통과", () => {
86
- expect(validator.parse(true)).toBe(true);
87
- });
85
+ it("true 통과", () => {
86
+ expect(validator.parse(true)).toBe(true);
87
+ });
88
88
 
89
- it("false 통과", () => {
90
- expect(validator.parse(false)).toBe(false);
91
- });
89
+ it("false 통과", () => {
90
+ expect(validator.parse(false)).toBe(false);
91
+ });
92
92
 
93
- it("문자열 → 에러", () => {
94
- expect(() => validator.parse("true")).toThrow("Expected boolean");
95
- });
93
+ it("문자열 → 에러", () => {
94
+ expect(() => validator.parse("true")).toThrow("Expected boolean");
95
+ });
96
96
 
97
- it("숫자 → 에러", () => {
98
- expect(() => validator.parse(1)).toThrow("Expected boolean");
99
- });
97
+ it("숫자 → 에러", () => {
98
+ expect(() => validator.parse(1)).toThrow("Expected boolean");
99
+ });
100
100
  });
101
101
 
102
102
  // ─── v.any() ────────────────────────────────────────────
103
103
 
104
104
  describe("v.any()", () => {
105
- const validator = v.any();
105
+ const validator = v.any();
106
106
 
107
- it("모든 값 통과 — 문자열", () => {
108
- expect(validator.parse("hello")).toBe("hello");
109
- });
107
+ it("모든 값 통과 — 문자열", () => {
108
+ expect(validator.parse("hello")).toBe("hello");
109
+ });
110
110
 
111
- it("모든 값 통과 — 숫자", () => {
112
- expect(validator.parse(42)).toBe(42);
113
- });
111
+ it("모든 값 통과 — 숫자", () => {
112
+ expect(validator.parse(42)).toBe(42);
113
+ });
114
114
 
115
- it("모든 값 통과 — null", () => {
116
- expect(validator.parse(null)).toBe(null);
117
- });
115
+ it("모든 값 통과 — null", () => {
116
+ expect(validator.parse(null)).toBe(null);
117
+ });
118
118
 
119
- it("모든 값 통과 — undefined", () => {
120
- expect(validator.parse(undefined)).toBe(undefined);
121
- });
119
+ it("모든 값 통과 — undefined", () => {
120
+ expect(validator.parse(undefined)).toBe(undefined);
121
+ });
122
122
 
123
- it("모든 값 통과 — 객체", () => {
124
- const obj = { key: "value" };
125
- expect(validator.parse(obj)).toBe(obj);
126
- });
123
+ it("모든 값 통과 — 객체", () => {
124
+ const obj = { key: "value" };
125
+ expect(validator.parse(obj)).toBe(obj);
126
+ });
127
127
  });
128
128
 
129
129
  // ─── v.optional() ───────────────────────────────────────
130
130
 
131
131
  describe("v.optional()", () => {
132
- const validator = v.optional(v.string());
132
+ const validator = v.optional(v.string());
133
133
 
134
- it("문자열 통과", () => {
135
- expect(validator.parse("hello")).toBe("hello");
136
- });
134
+ it("문자열 통과", () => {
135
+ expect(validator.parse("hello")).toBe("hello");
136
+ });
137
137
 
138
- it("undefined → undefined 반환", () => {
139
- expect(validator.parse(undefined)).toBe(undefined);
140
- });
138
+ it("undefined → undefined 반환", () => {
139
+ expect(validator.parse(undefined)).toBe(undefined);
140
+ });
141
141
 
142
- it("null → undefined 반환", () => {
143
- expect(validator.parse(null)).toBe(undefined);
144
- });
142
+ it("null → undefined 반환", () => {
143
+ expect(validator.parse(null)).toBe(undefined);
144
+ });
145
145
 
146
- it("숫자 → 에러 (내부 validator 적용)", () => {
147
- expect(() => validator.parse(42)).toThrow("Expected string");
148
- });
146
+ it("숫자 → 에러 (내부 validator 적용)", () => {
147
+ expect(() => validator.parse(42)).toThrow("Expected string");
148
+ });
149
149
  });
150
150
 
151
151
  // ─── v.array() ──────────────────────────────────────────
152
152
 
153
153
  describe("v.array()", () => {
154
- const validator = v.array(v.string());
154
+ const validator = v.array(v.string());
155
155
 
156
- it("문자열 배열 통과", () => {
157
- expect(validator.parse(["a", "b", "c"])).toEqual(["a", "b", "c"]);
158
- });
156
+ it("문자열 배열 통과", () => {
157
+ expect(validator.parse(["a", "b", "c"])).toEqual(["a", "b", "c"]);
158
+ });
159
159
 
160
- it("빈 배열 통과", () => {
161
- expect(validator.parse([])).toEqual([]);
162
- });
160
+ it("빈 배열 통과", () => {
161
+ expect(validator.parse([])).toEqual([]);
162
+ });
163
163
 
164
- it("비배열 → 에러", () => {
165
- expect(() => validator.parse("not array")).toThrow("Expected array");
166
- });
164
+ it("비배열 → 에러", () => {
165
+ expect(() => validator.parse("not array")).toThrow("Expected array");
166
+ });
167
167
 
168
- it("원소 타입 불일치 → 에러", () => {
169
- expect(() => validator.parse([1, 2, 3])).toThrow("Expected string");
170
- });
168
+ it("원소 타입 불일치 → 에러", () => {
169
+ expect(() => validator.parse([1, 2, 3])).toThrow("Expected string");
170
+ });
171
171
 
172
- it("혼합 배열 → 에러", () => {
173
- expect(() => validator.parse(["ok", 42])).toThrow("Expected string");
174
- });
172
+ it("혼합 배열 → 에러", () => {
173
+ expect(() => validator.parse(["ok", 42])).toThrow("Expected string");
174
+ });
175
175
  });
176
176
 
177
177
  // ─── v.object() ─────────────────────────────────────────
178
178
 
179
179
  describe("v.object()", () => {
180
- const validator = v.object({
181
- name: v.string(),
182
- age: v.number(),
183
- });
184
-
185
- it("유효한 객체 통과", () => {
186
- expect(validator.parse({ name: "Alice", age: 30 })).toEqual({ name: "Alice", age: 30 });
187
- });
188
-
189
- it("비객체 → 에러", () => {
190
- expect(() => validator.parse("string")).toThrow("Expected object");
191
- });
192
-
193
- it("null → 에러", () => {
194
- expect(() => validator.parse(null)).toThrow("Expected object");
195
- });
196
-
197
- it("필드 타입 불일치 → 에러", () => {
198
- expect(() => validator.parse({ name: 42, age: 30 })).toThrow("Expected string");
199
- });
200
-
201
- it("필드 누락 → inner validator 에러", () => {
202
- expect(() => validator.parse({ name: "Alice" })).toThrow("Expected number");
203
- });
180
+ const validator = v.object({
181
+ name: v.string(),
182
+ age: v.number(),
183
+ });
184
+
185
+ it("유효한 객체 통과", () => {
186
+ expect(validator.parse({ name: "Alice", age: 30 })).toEqual({ name: "Alice", age: 30 });
187
+ });
188
+
189
+ it("비객체 → 에러", () => {
190
+ expect(() => validator.parse("string")).toThrow("Expected object");
191
+ });
192
+
193
+ it("null → 에러", () => {
194
+ expect(() => validator.parse(null)).toThrow("Expected object");
195
+ });
196
+
197
+ it("필드 타입 불일치 → 에러", () => {
198
+ expect(() => validator.parse({ name: 42, age: 30 })).toThrow("Expected string");
199
+ });
200
+
201
+ it("필드 누락 → inner validator 에러", () => {
202
+ expect(() => validator.parse({ name: "Alice" })).toThrow("Expected number");
203
+ });
204
204
  });
205
205
 
206
206
  // ─── 복합 중첩 검증 ────────────────────────────────────
207
207
 
208
208
  describe("복합 중첩 검증", () => {
209
- it("v.optional(v.array(v.string()))", () => {
210
- const validator = v.optional(v.array(v.string()));
211
- expect(validator.parse(undefined)).toBe(undefined);
212
- expect(validator.parse(["a", "b"])).toEqual(["a", "b"]);
213
- expect(() => validator.parse([1])).toThrow("Expected string");
214
- });
215
-
216
- it("v.array(v.object(...))", () => {
217
- const validator = v.array(v.object({
218
- id: v.number(),
219
- label: v.string(),
220
- }));
221
- expect(validator.parse([
222
- { id: 1, label: "A" },
223
- { id: 2, label: "B" },
224
- ])).toEqual([
225
- { id: 1, label: "A" },
226
- { id: 2, label: "B" },
227
- ]);
228
- });
229
-
230
- it("v.object({ tags: v.array(v.string()), meta: v.optional(v.object(...)) })", () => {
231
- const validator = v.object({
232
- tags: v.array(v.string()),
233
- meta: v.optional(v.object({ key: v.string() })),
234
- });
235
- expect(validator.parse({ tags: ["a"], meta: undefined })).toEqual({ tags: ["a"], meta: undefined });
236
- expect(validator.parse({ tags: [], meta: { key: "val" } })).toEqual({ tags: [], meta: { key: "val" } });
209
+ it("v.optional(v.array(v.string()))", () => {
210
+ const validator = v.optional(v.array(v.string()));
211
+ expect(validator.parse(undefined)).toBe(undefined);
212
+ expect(validator.parse(["a", "b"])).toEqual(["a", "b"]);
213
+ expect(() => validator.parse([1])).toThrow("Expected string");
214
+ });
215
+
216
+ it("v.array(v.object(...))", () => {
217
+ const validator = v.array(
218
+ v.object({
219
+ id: v.number(),
220
+ label: v.string(),
221
+ }),
222
+ );
223
+ expect(
224
+ validator.parse([
225
+ { id: 1, label: "A" },
226
+ { id: 2, label: "B" },
227
+ ]),
228
+ ).toEqual([
229
+ { id: 1, label: "A" },
230
+ { id: 2, label: "B" },
231
+ ]);
232
+ });
233
+
234
+ it("v.object({ tags: v.array(v.string()), meta: v.optional(v.object(...)) })", () => {
235
+ const validator = v.object({
236
+ tags: v.array(v.string()),
237
+ meta: v.optional(v.object({ key: v.string() })),
237
238
  });
239
+ expect(validator.parse({ tags: ["a"], meta: undefined })).toEqual({ tags: ["a"], meta: undefined });
240
+ expect(validator.parse({ tags: [], meta: { key: "val" } })).toEqual({ tags: [], meta: { key: "val" } });
241
+ });
238
242
  });
239
243
 
240
244
  // ─── parseArgs() ────────────────────────────────────────
241
245
 
242
246
  describe("parseArgs()", () => {
243
- it("schema 없으면 args 그대로 반환", () => {
244
- expect(parseArgs(null, { foo: 1 })).toEqual({ foo: 1 });
245
- expect(parseArgs(undefined, "hello")).toBe("hello");
246
- });
247
-
248
- it("Validator 스키마로 직접 파싱", () => {
249
- const schema = v.string();
250
- expect(parseArgs(schema, "hello")).toBe("hello");
251
- });
252
-
253
- it("Validator 스키마 검증 실패 → GencowValidationError", () => {
254
- const schema = v.string();
255
- expect(() => parseArgs(schema, 42)).toThrow(GencowValidationError);
256
- });
257
-
258
- it("Shorthand record 스키마 (Convex 스타일)", () => {
259
- const schema = { title: v.string(), count: v.number() };
260
- expect(parseArgs(schema, { title: "Hello", count: 5 })).toEqual({ title: "Hello", count: 5 });
261
- });
262
-
263
- it("Shorthand record 검증 실패 → GencowValidationError + 필드명 포함", () => {
264
- const schema = { title: v.string(), count: v.number() };
265
- try {
266
- parseArgs(schema, { title: 42, count: 5 });
267
- expect(true).toBe(false); // 여기 도달하면 안 됨
268
- } catch (e) {
269
- expect(e).toBeInstanceOf(GencowValidationError);
270
- expect((e as Error).message).toContain("title");
271
- }
272
- });
273
-
274
- it("Shorthand record에 비객체 전달 → GencowValidationError", () => {
275
- const schema = { title: v.string() };
276
- expect(() => parseArgs(schema, "not an object")).toThrow(GencowValidationError);
277
- });
278
-
279
- it("GencowValidationError.statusCode === 400", () => {
280
- const err = new GencowValidationError("test");
281
- expect(err.statusCode).toBe(400);
282
- expect(err.name).toBe("GencowValidationError");
283
- });
284
-
285
- // ─── 빈 스키마 passthrough (FormData 업로드 버그 회귀 방지) ────
286
-
287
- it("빈 스키마 {} → args 전체 passthrough (FormData file 필드 보존)", () => {
288
- const schema = {};
289
- const args = { file: new File(["hello"], "test.txt"), _mutation: "upload.store" };
290
- const result = parseArgs(schema, args);
291
- // 빈 스키마이므로 args가 그대로 반환되어야 함 (file 포함)
292
- expect(result).toBe(args); // 참조 동일
293
- expect(result.file).toBeInstanceOf(File);
294
- expect(result._mutation).toBe("upload.store");
295
- });
296
-
297
- it("빈 스키마 {} + 일반 객체 → passthrough", () => {
298
- const schema = {};
299
- const args = { name: "test", count: 42, nested: { a: 1 } };
300
- const result = parseArgs(schema, args);
301
- expect(result).toBe(args);
302
- expect(result.name).toBe("test");
303
- expect(result.count).toBe(42);
304
- });
305
-
306
- it("빈 스키마 {} + 빈 args {} → 빈 객체 반환", () => {
307
- const result = parseArgs({}, {});
308
- expect(result).toEqual({});
309
- });
310
-
311
- it("키가 있는 스키마는 여전히 지정된 키만 추출 (file 제거됨)", () => {
312
- const schema = { title: v.string() };
313
- const args = { title: "hello", file: "should-be-stripped", extra: 123 };
314
- const result = parseArgs(schema, args);
315
- expect(result).toEqual({ title: "hello" });
316
- expect(result.file).toBeUndefined();
317
- expect(result.extra).toBeUndefined();
318
- });
247
+ it("schema 없으면 args 그대로 반환", () => {
248
+ expect(parseArgs(null, { foo: 1 })).toEqual({ foo: 1 });
249
+ expect(parseArgs(undefined, "hello")).toBe("hello");
250
+ });
251
+
252
+ it("Validator 스키마로 직접 파싱", () => {
253
+ const schema = v.string();
254
+ expect(parseArgs(schema, "hello")).toBe("hello");
255
+ });
256
+
257
+ it("Validator 스키마 검증 실패 → GencowValidationError", () => {
258
+ const schema = v.string();
259
+ expect(() => parseArgs(schema, 42)).toThrow(GencowValidationError);
260
+ });
261
+
262
+ it("Shorthand record 스키마 (Convex 스타일)", () => {
263
+ const schema = { title: v.string(), count: v.number() };
264
+ expect(parseArgs(schema, { title: "Hello", count: 5 })).toEqual({ title: "Hello", count: 5 });
265
+ });
266
+
267
+ it("Shorthand record 검증 실패 → GencowValidationError + 필드명 포함", () => {
268
+ const schema = { title: v.string(), count: v.number() };
269
+ try {
270
+ parseArgs(schema, { title: 42, count: 5 });
271
+ expect(true).toBe(false); // 여기 도달하면 안 됨
272
+ } catch (e) {
273
+ expect(e).toBeInstanceOf(GencowValidationError);
274
+ expect((e as Error).message).toContain("title");
275
+ }
276
+ });
277
+
278
+ it("Shorthand record에 비객체 전달 → GencowValidationError", () => {
279
+ const schema = { title: v.string() };
280
+ expect(() => parseArgs(schema, "not an object")).toThrow(GencowValidationError);
281
+ });
282
+
283
+ it("GencowValidationError.statusCode === 400", () => {
284
+ const err = new GencowValidationError("test");
285
+ expect(err.statusCode).toBe(400);
286
+ expect(err.name).toBe("GencowValidationError");
287
+ });
288
+
289
+ // ─── 빈 스키마 passthrough (FormData 업로드 버그 회귀 방지) ────
290
+
291
+ it("빈 스키마 {} → args 전체 passthrough (FormData file 필드 보존)", () => {
292
+ const schema = {};
293
+ const args = { file: new File(["hello"], "test.txt"), _mutation: "upload.store" };
294
+ const result = parseArgs(schema, args);
295
+ // 빈 스키마이므로 args가 그대로 반환되어야 함 (file 포함)
296
+ expect(result).toBe(args); // 참조 동일
297
+ expect(result.file).toBeInstanceOf(File);
298
+ expect(result._mutation).toBe("upload.store");
299
+ });
300
+
301
+ it("빈 스키마 {} + 일반 객체 → passthrough", () => {
302
+ const schema = {};
303
+ const args = { name: "test", count: 42, nested: { a: 1 } };
304
+ const result = parseArgs(schema, args);
305
+ expect(result).toBe(args);
306
+ expect(result.name).toBe("test");
307
+ expect(result.count).toBe(42);
308
+ });
309
+
310
+ it("빈 스키마 {} + 빈 args {} → 빈 객체 반환", () => {
311
+ const result = parseArgs({}, {});
312
+ expect(result).toEqual({});
313
+ });
314
+
315
+ it("키가 있는 스키마는 여전히 지정된 키만 추출 (file 제거됨)", () => {
316
+ const schema = { title: v.string() };
317
+ const args = { title: "hello", file: "should-be-stripped", extra: 123 };
318
+ const result = parseArgs(schema, args);
319
+ expect(result).toEqual({ title: "hello" });
320
+ expect(result.file).toBeUndefined();
321
+ expect(result.extra).toBeUndefined();
322
+ });
319
323
  });