@uploadista/server 0.0.10 → 0.0.12

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.
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Type tests for plugin-types.ts
3
+ *
4
+ * These tests verify that plugin type utilities correctly validate plugin
5
+ * requirements and generate appropriate error messages at compile time.
6
+ */
7
+
8
+ import { Effect, Layer } from "effect";
9
+ import { expectType } from "tsd";
10
+ import type {
11
+ ExtractFlowPluginRequirements,
12
+ InferFlowRequirements,
13
+ PluginServices,
14
+ PluginTuple,
15
+ TypeSafeFlowFunction,
16
+ ValidatePlugins,
17
+ } from "../src/core/plugin-types";
18
+
19
+ // ============================================================================
20
+ // Test Services and Layers
21
+ // ============================================================================
22
+
23
+ class ImagePlugin {
24
+ readonly _tag = "ImagePlugin";
25
+ resize(data: Buffer, width: number): Buffer {
26
+ return data;
27
+ }
28
+ }
29
+
30
+ class ZipPlugin {
31
+ readonly _tag = "ZipPlugin";
32
+ compress(files: Buffer[]): Buffer {
33
+ return Buffer.from([]);
34
+ }
35
+ }
36
+
37
+ class VideoPlugin {
38
+ readonly _tag = "VideoPlugin";
39
+ transcode(data: Buffer): Buffer {
40
+ return data;
41
+ }
42
+ }
43
+
44
+ // Create test layers
45
+ const imageLayer = Layer.succeed(ImagePlugin, new ImagePlugin());
46
+ const zipLayer = Layer.succeed(ZipPlugin, new ZipPlugin());
47
+ const videoLayer = Layer.succeed(VideoPlugin, new VideoPlugin());
48
+
49
+ // ============================================================================
50
+ // PluginTuple Tests
51
+ // ============================================================================
52
+
53
+ // Test: Should accept valid plugin tuples
54
+ expectType<PluginTuple>([imageLayer] as const);
55
+ expectType<PluginTuple>([imageLayer, zipLayer] as const);
56
+ expectType<PluginTuple>([imageLayer, zipLayer, videoLayer] as const);
57
+
58
+ // Test: Should accept empty tuple
59
+ expectType<PluginTuple>([] as const);
60
+
61
+ // ============================================================================
62
+ // PluginServices Tests
63
+ // ============================================================================
64
+
65
+ // Test: Should extract union of services from plugin tuple
66
+ expectType<ImagePlugin>({} as PluginServices<[typeof imageLayer]>);
67
+
68
+ expectType<ImagePlugin | ZipPlugin>(
69
+ {} as PluginServices<[typeof imageLayer, typeof zipLayer]>,
70
+ );
71
+
72
+ expectType<ImagePlugin | ZipPlugin | VideoPlugin>(
73
+ {} as PluginServices<[typeof imageLayer, typeof zipLayer, typeof videoLayer]>,
74
+ );
75
+
76
+ // Test: Should return never for empty tuple
77
+ expectType<never>({} as PluginServices<[]>);
78
+
79
+ // ============================================================================
80
+ // TypeSafeFlowFunction Tests
81
+ // ============================================================================
82
+
83
+ // Test: Should accept flow functions with matching requirements
84
+ const imageFlowFn: TypeSafeFlowFunction<ImagePlugin> = (
85
+ flowId: string,
86
+ clientId: string,
87
+ ) =>
88
+ Effect.gen(function* () {
89
+ const image = yield* ImagePlugin;
90
+ return {} as any;
91
+ });
92
+
93
+ expectType<TypeSafeFlowFunction<ImagePlugin>>(imageFlowFn);
94
+
95
+ const multiPluginFlowFn: TypeSafeFlowFunction<ImagePlugin | ZipPlugin> = (
96
+ flowId: string,
97
+ clientId: string,
98
+ ) =>
99
+ Effect.gen(function* () {
100
+ const image = yield* ImagePlugin;
101
+ const zip = yield* ZipPlugin;
102
+ return {} as any;
103
+ });
104
+
105
+ expectType<TypeSafeFlowFunction<ImagePlugin | ZipPlugin>>(multiPluginFlowFn);
106
+
107
+ // Test: Should accept flow with no requirements
108
+ const noPluginFlowFn: TypeSafeFlowFunction<never> = (
109
+ flowId: string,
110
+ clientId: string,
111
+ ) => Effect.succeed({} as any);
112
+
113
+ expectType<TypeSafeFlowFunction<never>>(noPluginFlowFn);
114
+
115
+ // ============================================================================
116
+ // ValidatePlugins Tests - Success Cases
117
+ // ============================================================================
118
+
119
+ // Test: Should return true when all requirements are satisfied
120
+ type ValidSinglePlugin = ValidatePlugins<[typeof imageLayer], ImagePlugin>;
121
+ expectType<true>({} as ValidSinglePlugin);
122
+
123
+ type ValidMultiplePlugins = ValidatePlugins<
124
+ [typeof imageLayer, typeof zipLayer],
125
+ ImagePlugin | ZipPlugin
126
+ >;
127
+ expectType<true>({} as ValidMultiplePlugins);
128
+
129
+ // Test: Should return true when plugins provide more than required
130
+ type ValidExtraPlugins = ValidatePlugins<
131
+ [typeof imageLayer, typeof zipLayer, typeof videoLayer],
132
+ ImagePlugin | ZipPlugin
133
+ >;
134
+ expectType<true>({} as ValidExtraPlugins);
135
+
136
+ // Test: Should return true when no requirements
137
+ type ValidNoRequirements = ValidatePlugins<[typeof imageLayer], never>;
138
+ expectType<true>({} as ValidNoRequirements);
139
+
140
+ type ValidEmptyPluginsNoRequirements = ValidatePlugins<[], never>;
141
+ expectType<true>({} as ValidEmptyPluginsNoRequirements);
142
+
143
+ // ============================================================================
144
+ // ValidatePlugins Tests - Error Cases
145
+ // ============================================================================
146
+
147
+ // Test: Should return error object when plugins are missing
148
+ type MissingSinglePlugin = ValidatePlugins<[], ImagePlugin>;
149
+
150
+ // Should NOT be true
151
+ type MissingSinglePluginIsTrue = MissingSinglePlugin extends true
152
+ ? true
153
+ : false;
154
+ expectType<false>({} as MissingSinglePluginIsTrue);
155
+
156
+ // Should have error properties
157
+ expectType<"MISSING_REQUIRED_PLUGINS">(
158
+ {} as MissingSinglePlugin extends { readonly __error: infer E } ? E : never,
159
+ );
160
+
161
+ expectType<"Missing required plugins. Check __missing field for details.">(
162
+ {} as MissingSinglePlugin extends { readonly __message: infer M } ? M : never,
163
+ );
164
+
165
+ expectType<ImagePlugin>(
166
+ {} as MissingSinglePlugin extends { readonly __required: infer R }
167
+ ? R
168
+ : never,
169
+ );
170
+
171
+ expectType<never>(
172
+ {} as MissingSinglePlugin extends { readonly __provided: infer P }
173
+ ? P
174
+ : never,
175
+ );
176
+
177
+ expectType<ImagePlugin>(
178
+ {} as MissingSinglePlugin extends { readonly __missing: infer M } ? M : never,
179
+ );
180
+
181
+ expectType<"Add the missing plugins to your server configuration's plugins array.">(
182
+ {} as MissingSinglePlugin extends { readonly __hint: infer H } ? H : never,
183
+ );
184
+
185
+ // Test: Should show partial missing plugins
186
+ type PartiallyMissingPlugins = ValidatePlugins<
187
+ [typeof imageLayer],
188
+ ImagePlugin | ZipPlugin
189
+ >;
190
+
191
+ expectType<ZipPlugin>(
192
+ {} as PartiallyMissingPlugins extends { readonly __missing: infer M }
193
+ ? M
194
+ : never,
195
+ );
196
+
197
+ expectType<ImagePlugin>(
198
+ {} as PartiallyMissingPlugins extends { readonly __provided: infer P }
199
+ ? P
200
+ : never,
201
+ );
202
+
203
+ // Test: Should show all missing plugins
204
+ type AllMissingPlugins = ValidatePlugins<
205
+ [],
206
+ ImagePlugin | ZipPlugin | VideoPlugin
207
+ >;
208
+
209
+ expectType<ImagePlugin | ZipPlugin | VideoPlugin>(
210
+ {} as AllMissingPlugins extends { readonly __missing: infer M } ? M : never,
211
+ );
212
+
213
+ // ============================================================================
214
+ // ExtractFlowPluginRequirements Tests
215
+ // ============================================================================
216
+
217
+ // Test: Should extract requirements from flow function
218
+ type ImageFlowRequirements = ExtractFlowPluginRequirements<typeof imageFlowFn>;
219
+ expectType<ImagePlugin>({} as ImageFlowRequirements);
220
+
221
+ type MultiPluginFlowRequirements = ExtractFlowPluginRequirements<
222
+ typeof multiPluginFlowFn
223
+ >;
224
+ expectType<ImagePlugin | ZipPlugin>({} as MultiPluginFlowRequirements);
225
+
226
+ type NoPluginFlowRequirements = ExtractFlowPluginRequirements<
227
+ typeof noPluginFlowFn
228
+ >;
229
+ expectType<never>({} as NoPluginFlowRequirements);
230
+
231
+ // ============================================================================
232
+ // InferFlowRequirements Tests
233
+ // ============================================================================
234
+
235
+ // Test: Should infer requirements from flow function type
236
+ type InferredImageFlow = InferFlowRequirements<typeof imageFlowFn>;
237
+ expectType<ImagePlugin>({} as InferredImageFlow);
238
+
239
+ type InferredMultiFlow = InferFlowRequirements<typeof multiPluginFlowFn>;
240
+ expectType<ImagePlugin | ZipPlugin>({} as InferredMultiFlow);
241
+
242
+ type InferredNoPluginFlow = InferFlowRequirements<typeof noPluginFlowFn>;
243
+ expectType<never>({} as InferredNoPluginFlow);
244
+
245
+ // ============================================================================
246
+ // Integration Tests - Realistic Usage Scenarios
247
+ // ============================================================================
248
+
249
+ // Scenario 1: Valid server configuration
250
+ const validPlugins = [imageLayer, zipLayer] as const;
251
+ const validFlow: TypeSafeFlowFunction<ImagePlugin | ZipPlugin> = (
252
+ flowId,
253
+ clientId,
254
+ ) =>
255
+ Effect.gen(function* () {
256
+ yield* ImagePlugin;
257
+ yield* ZipPlugin;
258
+ return {} as any;
259
+ });
260
+
261
+ type ValidConfig = ValidatePlugins<
262
+ typeof validPlugins,
263
+ ExtractFlowPluginRequirements<typeof validFlow>
264
+ >;
265
+ expectType<true>({} as ValidConfig);
266
+
267
+ // Scenario 2: Invalid server configuration - missing plugin
268
+ const incompletePlugins = [imageLayer] as const;
269
+ const multiPluginFlow: TypeSafeFlowFunction<ImagePlugin | ZipPlugin> = (
270
+ flowId,
271
+ clientId,
272
+ ) =>
273
+ Effect.gen(function* () {
274
+ yield* ImagePlugin;
275
+ yield* ZipPlugin;
276
+ return {} as any;
277
+ });
278
+
279
+ type InvalidConfig = ValidatePlugins<
280
+ typeof incompletePlugins,
281
+ ExtractFlowPluginRequirements<typeof multiPluginFlow>
282
+ >;
283
+
284
+ // Should fail validation
285
+ type InvalidConfigIsTrue = InvalidConfig extends true ? true : false;
286
+ expectType<false>({} as InvalidConfigIsTrue);
287
+
288
+ // Should show ZipPlugin as missing
289
+ expectType<ZipPlugin>(
290
+ {} as InvalidConfig extends { readonly __missing: infer M } ? M : never,
291
+ );
292
+
293
+ // Scenario 3: Valid configuration with extra plugins
294
+ const extraPlugins = [imageLayer, zipLayer, videoLayer] as const;
295
+ const simpleFlow: TypeSafeFlowFunction<ImagePlugin> = (flowId, clientId) =>
296
+ Effect.gen(function* () {
297
+ yield* ImagePlugin;
298
+ return {} as any;
299
+ });
300
+
301
+ type ExtraPluginsConfig = ValidatePlugins<
302
+ typeof extraPlugins,
303
+ ExtractFlowPluginRequirements<typeof simpleFlow>
304
+ >;
305
+ expectType<true>({} as ExtraPluginsConfig);
306
+
307
+ // Scenario 4: No plugins, no requirements - valid
308
+ const noPlugins = [] as const;
309
+ const noRequirementsFlow: TypeSafeFlowFunction<never> = (flowId, clientId) =>
310
+ Effect.succeed({} as any);
311
+
312
+ type NoPluginsConfig = ValidatePlugins<
313
+ typeof noPlugins,
314
+ ExtractFlowPluginRequirements<typeof noRequirementsFlow>
315
+ >;
316
+ expectType<true>({} as NoPluginsConfig);
317
+
318
+ // ============================================================================
319
+ // Error Message Quality Tests
320
+ // ============================================================================
321
+
322
+ // Test: Error messages should be descriptive and actionable
323
+ type ErrorWithGoodMessages = ValidatePlugins<[], ImagePlugin | ZipPlugin>;
324
+
325
+ // Verify all required error properties exist
326
+ type HasError = ErrorWithGoodMessages extends {
327
+ readonly __error: string;
328
+ readonly __message: string;
329
+ readonly __required: unknown;
330
+ readonly __provided: unknown;
331
+ readonly __missing: unknown;
332
+ readonly __hint: string;
333
+ }
334
+ ? true
335
+ : false;
336
+ expectType<true>({} as HasError);
337
+
338
+ // Verify error code is specific
339
+ type ErrorCode = ErrorWithGoodMessages extends {
340
+ readonly __error: "MISSING_REQUIRED_PLUGINS";
341
+ }
342
+ ? true
343
+ : false;
344
+ expectType<true>({} as ErrorCode);
345
+
346
+ // Verify message is helpful
347
+ type HasHelpfulMessage = ErrorWithGoodMessages extends {
348
+ readonly __message: "Missing required plugins. Check __missing field for details.";
349
+ }
350
+ ? true
351
+ : false;
352
+ expectType<true>({} as HasHelpfulMessage);
353
+
354
+ // Verify hint is actionable
355
+ type HasActionableHint = ErrorWithGoodMessages extends {
356
+ readonly __hint: "Add the missing plugins to your server configuration's plugins array.";
357
+ }
358
+ ? true
359
+ : false;
360
+ expectType<true>({} as HasActionableHint);
361
+
362
+ // ============================================================================
363
+ // Edge Cases
364
+ // ============================================================================
365
+
366
+ // Test: Union of multiple requirements
367
+ type UnionRequirements = ValidatePlugins<
368
+ [typeof imageLayer],
369
+ ImagePlugin | ZipPlugin | VideoPlugin
370
+ >;
371
+
372
+ expectType<ZipPlugin | VideoPlugin>(
373
+ {} as UnionRequirements extends { readonly __missing: infer M } ? M : never,
374
+ );
375
+
376
+ // Test: Complex nested requirements
377
+ type ComplexFlow = TypeSafeFlowFunction<
378
+ ImagePlugin | (ZipPlugin & { optional?: boolean })
379
+ >;
380
+ type ComplexRequirements = ExtractFlowPluginRequirements<ComplexFlow>;
381
+
382
+ // Should extract the union properly
383
+ type IsValidComplexExtraction = ComplexRequirements extends
384
+ | ImagePlugin
385
+ | ZipPlugin
386
+ ? true
387
+ : false;
388
+ expectType<true>({} as IsValidComplexExtraction);