@kumori/aurora-backend-handler 1.1.41 → 1.1.43

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.
@@ -8,6 +8,10 @@ import {
8
8
  ServiceSpecForm,
9
9
  } from "../api/deploy-service-helper";
10
10
 
11
+ jest.mock("@kumori/kumori-dsl-generator", () => ({
12
+ buildServiceDeploymentModule: jest.fn().mockResolvedValue(new Blob(["bundleDSL"])),
13
+ }));
14
+
11
15
  jest.mock("@kumori/kumori-module-generator", () => ({
12
16
  buildServiceDeploymentModuleWithLocalComponent: jest
13
17
  .fn()
@@ -22,11 +26,14 @@ jest.mock("../websocket-manager", () => ({
22
26
  getReferenceDomain: jest.fn().mockReturnValue("test-domain.com"),
23
27
  }));
24
28
 
29
+ // ── withDefaultValue ─────────────────────────────────────────────────────────
30
+
25
31
  describe("withDefaultValue", () => {
26
- it("retorna el valor si este no es null o undefined", () => {
32
+ it("retorna el valor si no es null ni undefined", () => {
27
33
  expect(withDefaultValue("valor", "default")).toBe("valor");
28
34
  expect(withDefaultValue(0, 10)).toBe(0);
29
35
  expect(withDefaultValue(false, true)).toBe(false);
36
+ expect(withDefaultValue("", "default")).toBe("");
30
37
  });
31
38
 
32
39
  it("retorna el valor por defecto si el valor es null o undefined", () => {
@@ -35,135 +42,202 @@ describe("withDefaultValue", () => {
35
42
  });
36
43
  });
37
44
 
45
+ // ── handleParametersToGenerateData ───────────────────────────────────────────
46
+
38
47
  describe("handleParametersToGenerateData", () => {
39
- it("maneja parámetros básicos (string, number, boolean)", () => {
40
- const formParams = [
41
- { name: "param1", value: "valor1", type: "string" },
42
- { name: "param2", value: "42", type: "number" },
43
- { name: "param3", value: "true", type: "boolean" },
44
- ];
45
- const result = handleParametersToGenerateData(formParams, []);
48
+ it("maneja tipo string", () => {
49
+ const result = handleParametersToGenerateData(
50
+ [{ name: "p1", value: "v1", type: "string" }],
51
+ [],
52
+ );
53
+ expect(result.parameters).toContainEqual({ name: "p1", type: "string", defaultValue: "v1" });
54
+ expect(result.environment).toContainEqual({ varName: "p1", param: "p1" });
55
+ });
46
56
 
47
- expect(result.parameters).toHaveLength(3);
48
- expect(result.environment).toHaveLength(3);
49
- expect(result.parameters[0]).toEqual({
50
- name: "param1",
51
- type: "string",
52
- defaultValue: "valor1",
53
- });
54
- expect(result.parameters[1].defaultValue).toBe(42);
55
- expect(result.parameters[2].defaultValue).toBe(true);
57
+ it("maneja tipo number", () => {
58
+ const result = handleParametersToGenerateData(
59
+ [{ name: "p1", value: "42", type: "number" }],
60
+ [],
61
+ );
62
+ expect(result.parameters[0].defaultValue).toBe(42);
63
+ expect(result.environment).toContainEqual({ varName: "p1", param: "p1" });
56
64
  });
57
65
 
58
- it("maneja parámetros de tipo secret", () => {
59
- const formParams = [{ name: "sec1", value: "secretValue", type: "secret" }];
60
- const result = handleParametersToGenerateData(formParams, []);
66
+ it("maneja tipo boolean con string 'true'", () => {
67
+ const result = handleParametersToGenerateData(
68
+ [{ name: "p1", value: "true", type: "boolean" }],
69
+ [],
70
+ );
71
+ expect(result.parameters[0].defaultValue).toBe(true);
72
+ });
61
73
 
62
- expect(result.environment).toContainEqual({
63
- varName: "sec1",
64
- secret: "secretValue",
65
- });
66
- expect(result.resources).toContainEqual({
67
- secret: { name: "secretValue" },
68
- });
74
+ it("maneja tipo bool con string 'false'", () => {
75
+ const result = handleParametersToGenerateData(
76
+ [{ name: "p1", value: "false", type: "bool" }],
77
+ [],
78
+ );
79
+ expect(result.parameters[0].defaultValue).toBe(false);
69
80
  });
70
81
 
71
- it("maneja parámetros de tipo file", () => {
72
- const formParams = [
73
- { name: "file1", value: "/path/to/file", type: "file" },
74
- ];
75
- const result = handleParametersToGenerateData(formParams, []);
82
+ it("maneja tipo bool con valor boolean true", () => {
83
+ const result = handleParametersToGenerateData(
84
+ [{ name: "p1", value: true as any, type: "bool" }],
85
+ [],
86
+ );
87
+ expect(result.parameters[0].defaultValue).toBe(true);
88
+ });
76
89
 
77
- expect(result.fileSystem).toContainEqual({
78
- path: "/app/config/file1",
79
- param: "file1",
80
- });
90
+ it("maneja tipo secret", () => {
91
+ const result = handleParametersToGenerateData(
92
+ [{ name: "sec1", value: "secretValue", type: "secret" }],
93
+ [],
94
+ );
95
+ expect(result.environment).toContainEqual({ varName: "sec1", secret: "sec1" });
96
+ expect(result.resources).toContainEqual({ secret: { name: "secretValue" } });
97
+ expect(result.parameters).toHaveLength(0);
81
98
  });
82
99
 
83
- it("maneja parámetros de tipo fileContent", () => {
84
- const formParams = [
85
- {
86
- name: "file1",
87
- value: "/custom/path",
88
- type: "fileContent",
89
- content: "contenido",
90
- },
91
- ];
92
- const result = handleParametersToGenerateData(formParams, []);
100
+ it("maneja tipo file", () => {
101
+ const result = handleParametersToGenerateData(
102
+ [{ name: "myconfig", value: "/path/to/file", type: "file" }],
103
+ [],
104
+ );
105
+ expect(result.parameters[0].name).toBe("CONFIG_FILE_0");
106
+ expect(result.parameters[0].type).toBe("string");
107
+ expect(result.parameters[0].defaultValue).toBe("/path/to/file");
108
+ expect(result.fileSystem).toContainEqual({ path: "myconfig", param: "CONFIG_FILE_0" });
109
+ });
93
110
 
111
+ it("maneja tipo fileContent con content", () => {
112
+ const result = handleParametersToGenerateData(
113
+ [{ name: "f1", value: "/custom/path", type: "fileContent", content: "contenido" }],
114
+ [],
115
+ );
94
116
  expect(result.parameters[0].defaultValue).toBe("contenido");
95
117
  expect(result.fileSystem[0].path).toBe("/custom/path");
118
+ expect(result.fileSystem[0].param).toBe("CONFIG_FILE_0");
96
119
  });
97
120
 
98
- it("maneja parámetros de tipo volume", () => {
99
- const formParams = [{ name: "vol1", value: "/data", type: "volume" }];
100
- const result = handleParametersToGenerateData(formParams, []);
121
+ it("maneja tipo fileContent sin content (usa value)", () => {
122
+ const result = handleParametersToGenerateData(
123
+ [{ name: "f1", value: "/some/path", type: "fileContent" }],
124
+ [],
125
+ );
126
+ expect(result.parameters[0].defaultValue).toBe("/some/path");
127
+ expect(result.fileSystem[0].path).toBe("/some/path");
128
+ });
101
129
 
130
+ it("maneja tipo volume sin content (usa value)", () => {
131
+ const result = handleParametersToGenerateData(
132
+ [{ name: "vol1", value: "/data", type: "volume" }],
133
+ [],
134
+ );
102
135
  expect(result.resources).toContainEqual({ volume: { name: "vol1" } });
103
- expect(result.fileSystem).toContainEqual({
104
- path: "/data",
105
- resourceVolume: "vol1",
106
- });
136
+ expect(result.fileSystem).toContainEqual({ path: "/data", resourceVolume: "vol1" });
107
137
  });
108
138
 
109
- it("maneja parámetros de tipo serviceUrl", () => {
110
- const formParams = [
111
- { name: "url1", value: "https://api.example.com", type: "serviceUrl" },
112
- ];
113
- const result = handleParametersToGenerateData(formParams, []);
139
+ it("maneja tipo volume con content", () => {
140
+ const result = handleParametersToGenerateData(
141
+ [{ name: "vol1", value: "/data", type: "volume", content: "/mount/path" }],
142
+ [],
143
+ );
144
+ expect(result.resources).toContainEqual({ volume: { name: "vol1" } });
145
+ expect(result.fileSystem).toContainEqual({ path: "/mount/path", resourceVolume: "vol1" });
146
+ });
114
147
 
148
+ it("maneja tipo serviceUrl", () => {
149
+ const result = handleParametersToGenerateData(
150
+ [{ name: "url1", value: "https://api.example.com", type: "serviceUrl" }],
151
+ [],
152
+ );
115
153
  expect(result.parameters[0].defaultValue).toBe("https://api.example.com");
154
+ expect(result.environment).toContainEqual({ varName: "url1", param: "url1" });
116
155
  });
117
156
 
118
- it("maneja tipos desconocidos", () => {
119
- const consoleSpy = jest.spyOn(console, "warn").mockImplementation();
120
- const formParams = [{ name: "param1", value: "valor", type: "unknown" }];
121
-
122
- handleParametersToGenerateData(formParams, []);
123
-
157
+ it("maneja tipos desconocidos con warn", () => {
158
+ const consoleSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
159
+ const result = handleParametersToGenerateData(
160
+ [{ name: "p1", value: "v1", type: "unknown" }],
161
+ [],
162
+ );
124
163
  expect(consoleSpy).toHaveBeenCalledWith("Unknown parameter type: unknown");
164
+ expect(result.parameters[0].type).toBe("string");
125
165
  consoleSpy.mockRestore();
126
166
  });
127
167
 
128
- it("maneja recursos de tipo secret", () => {
129
- const formResources = [
130
- { name: "res1", type: "secret", value: "secretValue" },
131
- ];
132
- const result = handleParametersToGenerateData([], formResources);
168
+ it("maneja tipos desconocidos con value undefined", () => {
169
+ const consoleSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
170
+ handleParametersToGenerateData(
171
+ [{ name: "p1", value: undefined as any, type: "unknown" }],
172
+ [],
173
+ );
174
+ expect(consoleSpy).toHaveBeenCalled();
175
+ consoleSpy.mockRestore();
176
+ });
133
177
 
178
+ it("maneja formResources de tipo secret", () => {
179
+ const result = handleParametersToGenerateData([], [
180
+ { name: "res1", type: "secret", value: "secretValue" },
181
+ ]);
134
182
  expect(result.resources).toContainEqual({ secret: { name: "res1" } });
183
+ expect(result.environment).toContainEqual({ varName: "res1", secret: "res1" });
184
+ });
185
+
186
+ it("maneja formResources de tipo volume", () => {
187
+ const result = handleParametersToGenerateData([], [
188
+ { name: "vol1", type: "volume", key: "/mnt/data" },
189
+ ]);
190
+ expect(result.resources).toContainEqual({ volume: { name: "vol1" } });
191
+ expect(result.fileSystem).toContainEqual({ path: "/mnt/data", resourceVolume: "vol1" });
192
+ });
193
+
194
+ it("maneja formResources de tipo volume sin key", () => {
195
+ const result = handleParametersToGenerateData([], [
196
+ { name: "vol1", type: "volume" },
197
+ ]);
198
+ expect(result.fileSystem[0].path).toBe("");
199
+ });
200
+
201
+ it("maneja formResources de tipo desconocido con warn", () => {
202
+ const consoleSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
203
+ handleParametersToGenerateData([], [{ name: "r1", type: "unknown" }]);
204
+ expect(consoleSpy).toHaveBeenCalledWith("Unknown resource type: unknown");
205
+ consoleSpy.mockRestore();
206
+ });
207
+
208
+ it("maneja múltiples parámetros a la vez", () => {
209
+ const result = handleParametersToGenerateData(
210
+ [
211
+ { name: "s1", value: "v1", type: "string" },
212
+ { name: "n1", value: "10", type: "number" },
213
+ { name: "b1", value: "true", type: "boolean" },
214
+ ],
215
+ [],
216
+ );
217
+ expect(result.parameters).toHaveLength(3);
218
+ expect(result.environment).toHaveLength(3);
135
219
  });
136
220
  });
137
221
 
222
+ // ── transformServiceToForm ────────────────────────────────────────────────────
223
+
138
224
  describe("transformServiceToForm", () => {
139
225
  const createMockService = (overrides = {}): Service => ({
140
- id: "servicio1",
226
+ id: "s1",
141
227
  tenant: "tenant1",
142
228
  account: "account1",
143
229
  environment: "env1",
144
- name: "Servicio Prueba",
230
+ name: "TestService",
145
231
  logo: "",
146
232
  description: "",
147
233
  revisions: [],
148
- status: {
149
- message: "running",
150
- timestamp: "2024-01-01",
151
- args: [],
152
- code: "200",
153
- },
154
- role: [{ name: "servicio1", instances: [] }],
234
+ status: { message: "running", timestamp: "2024-01-01", args: [], code: "200" },
235
+ role: [{ name: "s1", instances: [] }],
155
236
  links: [],
156
237
  resources: [],
157
238
  parameters: [],
158
239
  usage: {
159
- current: {
160
- cpu: 2,
161
- memory: 512,
162
- storage: 100,
163
- volatileStorage: 10,
164
- nonReplicatedStorage: 10,
165
- persistentStorage: 20,
166
- },
240
+ current: { cpu: 2, memory: 512, storage: 100, volatileStorage: 10, nonReplicatedStorage: 10, persistentStorage: 20 },
167
241
  limit: {
168
242
  cpu: { max: 4, min: 1 },
169
243
  memory: { max: 1024, min: 256 },
@@ -174,14 +248,11 @@ describe("transformServiceToForm", () => {
174
248
  },
175
249
  cost: 100,
176
250
  },
177
- minReplicas: 1,
178
- maxReplicas: 3,
179
- lastDeployed: "",
180
- project: "project1",
251
+ project: "proj1",
181
252
  registry: "docker.io",
182
- imageName: "miImagen",
183
- entrypoint: "/iniciar",
184
- cmd: "start",
253
+ imageName: "myImage",
254
+ entrypoint: "/start",
255
+ cmd: "run",
185
256
  serverChannels: [],
186
257
  clientChannels: [],
187
258
  duplexChannels: [],
@@ -189,231 +260,427 @@ describe("transformServiceToForm", () => {
189
260
  ...overrides,
190
261
  });
191
262
 
192
- it("transforma correctamente un objeto Service básico", () => {
193
- const service = createMockService({
194
- clientChannels: [{ name: "clientChannel1", from: "", to: "" }],
195
- serverChannels: [
196
- {
197
- name: "serverChannel1",
198
- from: "",
199
- to: "",
200
- port: 8080,
201
- isPublic: true,
202
- protocol: "http",
203
- portNum: 80,
204
- },
205
- ],
206
- });
207
-
208
- const form = transformServiceToForm(service);
209
-
263
+ it("transforma servicio básico correctamente", () => {
264
+ const form = transformServiceToForm(createMockService());
210
265
  expect(form.tenantId).toBe("tenant1");
211
266
  expect(form.accountId).toBe("account1");
212
- expect(form.serviceId).toBe("Servicio Prueba");
267
+ expect(form.environmentId).toBe("env1");
268
+ expect(form.serviceId).toBe("TestService");
213
269
  expect(form.cpuRequirements).toBe(4);
214
270
  expect(form.memoryRequirements).toBe(1024);
215
271
  expect(form.registryUrl).toBe("docker.io");
216
- expect(form.imageTag).toBe("miImagen");
217
- expect(form.clientChannels).toEqual([{ name: "clientChannel1" }]);
272
+ expect(form.imageTag).toBe("myImage");
273
+ expect(form.defaultExecutable).toEqual({ cmd: "run", entryPoint: "/start" });
274
+ expect(form.clientChannels).toEqual([]);
275
+ expect(form.channels).toEqual([]);
276
+ expect(form.clientChannelsExtra).toEqual([]);
277
+ });
278
+
279
+ it("transforma serverChannels con protocolo http a HTTPS", () => {
280
+ const service = createMockService({
281
+ serverChannels: [{ name: "web", port: 8080, isPublic: true, protocol: "http", portNum: 80 }],
282
+ });
283
+ const form = transformServiceToForm(service);
218
284
  expect(form.channels[0].protocol).toBe("HTTPS");
285
+ expect(form.channels[0].containerPort).toBe(8080);
286
+ expect(form.channels[0].isPublic).toBe(true);
287
+ expect(form.channels[0].publicPort).toBe("80");
219
288
  });
220
289
 
221
- it("maneja serverChannels con protocolo TCP", () => {
290
+ it("transforma serverChannels con protocolo tcp a TCP", () => {
222
291
  const service = createMockService({
223
- serverChannels: [
224
- {
225
- name: "tcpChannel",
226
- from: "",
227
- to: "",
228
- protocol: "tcp",
229
- },
230
- ],
292
+ serverChannels: [{ name: "tcp1", protocol: "tcp" }],
231
293
  });
294
+ const form = transformServiceToForm(service);
295
+ expect(form.channels[0].protocol).toBe("TCP");
296
+ });
232
297
 
298
+ it("transforma serverChannels con protocolo desconocido a TCP", () => {
299
+ const service = createMockService({
300
+ serverChannels: [{ name: "other", protocol: "grpc" }],
301
+ });
233
302
  const form = transformServiceToForm(service);
234
303
  expect(form.channels[0].protocol).toBe("TCP");
235
304
  });
236
305
 
237
- it("maneja scaling configuration", () => {
306
+ it("transforma clientChannels y duplexChannels", () => {
307
+ const service = createMockService({
308
+ clientChannels: [{ name: "clientCh" }],
309
+ duplexChannels: [{ name: "duplexCh" }],
310
+ });
311
+ const form = transformServiceToForm(service);
312
+ expect(form.clientChannels).toEqual([{ name: "clientCh" }]);
313
+ expect(form.clientChannelsExtra).toEqual([{ name: "duplexCh" }]);
314
+ });
315
+
316
+ it("transforma resources de tipo volume al fileSystem", () => {
238
317
  const service = createMockService({
239
- role: [
240
- {
241
- name: "servicio1",
242
- instances: [],
243
- scalling: {
244
- cpu: { up: "80", down: "20" },
245
- memory: { up: "80", down: "20" },
246
- instances: { max: 5, min: 1 },
247
- histeresys: "10",
248
- },
249
- },
318
+ resources: [
319
+ { name: "vol1", type: "volume", key: "/data" },
320
+ { name: "sec1", type: "secret", value: "mySecret" },
250
321
  ],
251
322
  });
323
+ const form = transformServiceToForm(service);
324
+ expect(form.fileSystem).toContainEqual({ path: "/data", resourceVolume: "vol1" });
325
+ expect(form.config.resources).toHaveLength(2);
326
+ });
327
+
328
+ it("transforma parameters", () => {
329
+ const service = createMockService({
330
+ parameters: [{ name: "env", value: "prod", type: "string" }],
331
+ });
332
+ const form = transformServiceToForm(service);
333
+ expect(form.config.parameters).toContainEqual({ name: "env", value: "prod", type: "string" });
334
+ });
252
335
 
336
+ it("transforma scaling configuration", () => {
337
+ const service = createMockService({
338
+ role: [{
339
+ name: "s1",
340
+ instances: [],
341
+ scalling: { cpu: { up: "80", down: "20" }, memory: { up: "80", down: "20" }, instances: { max: 5, min: 1 }, histeresys: "10" },
342
+ }],
343
+ });
253
344
  const form = transformServiceToForm(service);
254
345
  expect(form.scaling).toBeDefined();
255
346
  expect(form.scaling?.instances.max).toBe(5);
256
347
  });
348
+
349
+ it("transforma certificateResource, withMtls y caResource en channels", () => {
350
+ const service = createMockService({
351
+ serverChannels: [{ name: "web", protocol: "http", certificateResource: "mycert", withMtls: true, caResource: "myca" }],
352
+ });
353
+ const form = transformServiceToForm(service);
354
+ expect(form.channels[0].certificateResource).toBe("mycert");
355
+ expect(form.channels[0].withMtls).toBe(true);
356
+ expect(form.channels[0].caResource).toBe("myca");
357
+ });
257
358
  });
258
359
 
360
+ // ── generateServiceSpec ───────────────────────────────────────────────────────
361
+
259
362
  describe("generateServiceSpec", () => {
260
- const createMockForm = (overrides = {}): ServiceSpecForm => ({
363
+ const createMockForm = (overrides: Partial<ServiceSpecForm> = {}): ServiceSpecForm => ({
261
364
  tenantId: "tenant1",
262
365
  accountId: "account1",
263
366
  environmentId: "env1",
264
- serviceId: "servicio1",
367
+ serviceId: "svc1",
265
368
  cpuRequirements: 2,
266
369
  memoryRequirements: 512,
267
370
  registryUrl: "docker.io",
268
- imageTag: "miImagen",
371
+ imageTag: "myImage",
269
372
  clientChannels: [],
270
373
  channels: [],
271
374
  defaultExecutable: {},
272
- config: {
273
- parameters: [],
274
- resources: [],
275
- },
375
+ config: { parameters: [], resources: [] },
276
376
  ...overrides,
277
377
  });
278
378
 
279
- it("genera un ServiceWithLocalComponentSpec básico", async () => {
280
- const form = createMockForm({
281
- clientChannels: [{ name: "clientChannel1" }],
282
- });
283
-
284
- const serviceSpec = await generateServiceSpec(form);
379
+ it("genera spec básico sin channels ni recursos", async () => {
380
+ const spec = await generateServiceSpec(createMockForm());
381
+ expect(spec.type).toBe("service");
382
+ expect(spec.subtype).toBe("service_with_local_component");
383
+ expect(spec.tenantId).toBe("tenant1");
384
+ expect(spec.deploymentConfig.scale.detail["svc1"]).toBe(1);
385
+ expect(spec.local_components["svc1"]).toBeDefined();
386
+ });
285
387
 
286
- expect(serviceSpec.type).toBe("service");
287
- expect(serviceSpec.subtype).toBe("service_with_local_component");
288
- expect(serviceSpec.tenantId).toBe("tenant1");
289
- expect(serviceSpec.local_components["servicio1"].channels.client).toEqual([
290
- "clientChannel1",
291
- ]);
388
+ it("genera spec con clientChannels", async () => {
389
+ const spec = await generateServiceSpec(createMockForm({ clientChannels: [{ name: "ch1" }] }));
390
+ expect(spec.local_components["svc1"].channels.client).toContain("ch1");
391
+ expect(spec.topology).toContainEqual(
392
+ expect.objectContaining({ clientRole: "svc1", clientChannel: "ch1", serverRole: "self" }),
393
+ );
292
394
  });
293
395
 
294
- it("genera inbound roles para canales HTTPS públicos", async () => {
396
+ it("genera inbound role para canal HTTPS público sin certificateResource (cert por defecto)", async () => {
295
397
  const form = createMockForm({
296
- channels: [
297
- {
298
- channelName: "https1",
299
- containerPort: 443,
300
- isPublic: true,
301
- protocol: "HTTPS",
302
- domain: "example.com",
303
- },
304
- ],
398
+ channels: [{ channelName: "web", containerPort: 443, isPublic: true, protocol: "HTTPS", domain: "example.com" }],
305
399
  });
400
+ const spec = await generateServiceSpec(form);
401
+ const inbound = spec.roles.find((r) => r.name === "web_inbound");
402
+ expect(inbound).toBeDefined();
403
+ expect(inbound?.artifact.config.parameters).toContainEqual({ name: "type", value: "https", type: "string" });
404
+ expect(spec.config.resources).toContainEqual({ certificate: { name: "main_inbound_servercert" } });
405
+ const certDeploy = spec.deploymentConfig.resources.find((r: any) => r.certificate?.name === "main_inbound_servercert");
406
+ expect(certDeploy).toBeDefined();
407
+ });
306
408
 
307
- const serviceSpec = await generateServiceSpec(form);
409
+ it("genera inbound role para canal HTTPS público con certificateResource personalizado", async () => {
410
+ const form = createMockForm({
411
+ channels: [{ channelName: "web", containerPort: 443, isPublic: true, protocol: "HTTPS", domain: "example.com", certificateResource: "my-cert" }],
412
+ });
413
+ const spec = await generateServiceSpec(form);
414
+ const certConfig = spec.config.resources.find((r: any) => r.certificate?.name === "web_cert");
415
+ expect(certConfig).toBeDefined();
416
+ const certDeploy = spec.deploymentConfig.resources.find((r: any) => r.certificate?.name === "web_cert");
417
+ expect(certDeploy).toBeDefined();
418
+ expect((certDeploy as any).certificate.resource).toBe("my-cert");
419
+ });
308
420
 
309
- const inboundRole = serviceSpec.roles.find(
310
- (role) => role.name === "https1_inbound"
311
- );
312
- expect(inboundRole).toBeDefined();
313
- expect(inboundRole?.artifact.config.parameters).toContainEqual({
314
- name: "type",
315
- value: "https",
316
- type: "string",
421
+ it("genera inbound role para canal HTTPS con mTLS y caResource", async () => {
422
+ const form = createMockForm({
423
+ channels: [{
424
+ channelName: "web", containerPort: 443, isPublic: true, protocol: "HTTPS",
425
+ domain: "example.com", certificateResource: "my-cert", withMtls: true, caResource: "my-ca",
426
+ }],
317
427
  });
428
+ const spec = await generateServiceSpec(form);
429
+ const caConfig = spec.config.resources.find((r: any) => r.ca?.name === "web_ca");
430
+ expect(caConfig).toBeDefined();
431
+ const caDeploy = spec.deploymentConfig.resources.find((r: any) => r.ca?.name === "web_ca");
432
+ expect(caDeploy).toBeDefined();
318
433
  });
319
434
 
320
- it("genera inbound roles para canales TCP públicos", async () => {
435
+ it("genera inbound role para canal TCP público", async () => {
321
436
  const form = createMockForm({
322
- channels: [
323
- {
324
- channelName: "tcp1",
325
- containerPort: 9000,
326
- isPublic: true,
327
- protocol: "TCP",
328
- publicPort: "9000",
329
- },
330
- ],
437
+ channels: [{ channelName: "tcp1", containerPort: 9000, isPublic: true, protocol: "TCP", publicPort: "9000" }],
331
438
  });
439
+ const spec = await generateServiceSpec(form);
440
+ const inbound = spec.roles.find((r) => r.name === "tcp1_inbound");
441
+ expect(inbound).toBeDefined();
442
+ expect(inbound?.artifact.config.parameters).toContainEqual({ name: "type", value: "tcp", type: "string" });
443
+ const portDeploy = spec.deploymentConfig.resources.find((r: any) => r.port?.name === "tcp1_port");
444
+ expect(portDeploy).toBeDefined();
445
+ });
332
446
 
333
- const serviceSpec = await generateServiceSpec(form);
447
+ it("canal con containerPort 0 no añade port en channels.server", async () => {
448
+ const form = createMockForm({
449
+ channels: [{ channelName: "web", containerPort: 0, isPublic: false, protocol: "HTTPS" }],
450
+ });
451
+ const spec = await generateServiceSpec(form);
452
+ const ch = spec.channels.server.find((s: any) => s.name === "web");
453
+ expect(ch).toEqual({ name: "web" });
454
+ });
334
455
 
335
- const inboundRole = serviceSpec.roles.find(
336
- (role) => role.name === "tcp1_inbound"
456
+ it("genera topology para canal público", async () => {
457
+ const form = createMockForm({
458
+ channels: [{ channelName: "web", containerPort: 443, isPublic: true, protocol: "HTTPS" }],
459
+ });
460
+ const spec = await generateServiceSpec(form);
461
+ expect(spec.topology).toContainEqual(
462
+ expect.objectContaining({ clientRole: "web_inbound", clientChannel: "inbound" }),
463
+ );
464
+ expect(spec.topology).toContainEqual(
465
+ expect.objectContaining({ clientRole: "self", clientChannel: "web" }),
337
466
  );
338
- expect(inboundRole).toBeDefined();
339
467
  });
340
468
 
341
- it("maneja volúmenes con diferentes tipos", async () => {
342
- const consoleSpy = jest.spyOn(console, "warn").mockImplementation();
469
+ it("maneja recursos de tipo secret en config", async () => {
470
+ const form = createMockForm({
471
+ config: { parameters: [], resources: [{ name: "sec1", type: "secret", value: "my-secret" }] },
472
+ });
473
+ const spec = await generateServiceSpec(form);
474
+ expect(spec.config.resources).toContainEqual({ secret: { name: "sec1" } });
475
+ const secDeploy = spec.deploymentConfig.resources.find((r: any) => r.secret?.name === "sec1");
476
+ expect(secDeploy).toBeDefined();
477
+ });
343
478
 
479
+ it("maneja volumen sin size (referencia externa)", async () => {
344
480
  const form = createMockForm({
345
- config: {
346
- parameters: [],
347
- resources: [
348
- { name: "vol1", type: "volume", size: 5, kind: "volatile" },
349
- { name: "vol2", type: "volume", size: 10, kind: "persistent" },
350
- { name: "vol3", type: "volume", size: 15, kind: "nonreplicated" },
351
- ],
352
- },
481
+ config: { parameters: [], resources: [{ name: "vol1", type: "volume", value: "existing-vol" }] },
353
482
  });
483
+ const spec = await generateServiceSpec(form);
484
+ const volDeploy = spec.deploymentConfig.resources.find((r: any) => r.volume?.name === "vol1");
485
+ expect(volDeploy).toBeDefined();
486
+ expect((volDeploy as any).volume.resource).toBe("existing-vol");
487
+ });
354
488
 
355
- const serviceSpec = await generateServiceSpec(form);
489
+ it("maneja volúmenes con type volatile", async () => {
490
+ const form = createMockForm({
491
+ config: { parameters: [], resources: [{ name: "vol1", type: "volume", size: 5, kind: "volatile" } as any] },
492
+ });
493
+ const spec = await generateServiceSpec(form);
494
+ const volDeploy = spec.deploymentConfig.resources.find((r: any) => r.volume?.volume?.type === "volatile");
495
+ expect(volDeploy).toBeDefined();
496
+ expect((volDeploy as any).volume.volume.size).toBe(5000);
497
+ });
356
498
 
357
- const resources = serviceSpec.deploymentConfig.resources;
358
- expect(resources.some((r: any) => r.volume?.volume?.type === "volatile")).toBe(true);
359
- expect(resources.some((r: any) => r.volume?.volume?.type === "persistent")).toBe(true);
360
- expect(resources.some((r: any) => r.volume?.volume?.type === "nonreplicated")).toBe(true);
499
+ it("maneja volúmenes con type persistent", async () => {
500
+ const form = createMockForm({
501
+ config: { parameters: [], resources: [{ name: "vol2", type: "volume", size: 10, kind: "persistent" } as any] },
502
+ });
503
+ const spec = await generateServiceSpec(form);
504
+ const volDeploy = spec.deploymentConfig.resources.find((r: any) => r.volume?.volume?.type === "persistent");
505
+ expect(volDeploy).toBeDefined();
506
+ });
361
507
 
362
- consoleSpy.mockRestore();
508
+ it("maneja volúmenes con type nonreplicated (default)", async () => {
509
+ const form = createMockForm({
510
+ config: { parameters: [], resources: [{ name: "vol3", type: "volume", size: 15, kind: "nonreplicated" } as any] },
511
+ });
512
+ const spec = await generateServiceSpec(form);
513
+ const volDeploy = spec.deploymentConfig.resources.find((r: any) => r.volume?.volume?.type === "nonreplicated");
514
+ expect(volDeploy).toBeDefined();
363
515
  });
364
516
 
365
- it("maneja scaling configuration", async () => {
517
+ it("maneja volúmenes sin kind (usa nonreplicated por defecto)", async () => {
366
518
  const form = createMockForm({
367
- scaling: {
368
- cpu: { up: "80", down: "20" },
369
- memory: { up: "80", down: "20" },
370
- instances: { min: 1, max: 5 },
371
- histeresys: "10",
372
- },
519
+ config: { parameters: [], resources: [{ name: "vol4", type: "volume", size: 20 } as any] },
373
520
  });
521
+ const spec = await generateServiceSpec(form);
522
+ const volDeploy = spec.deploymentConfig.resources.find((r: any) => r.volume?.volume?.type === "nonreplicated");
523
+ expect(volDeploy).toBeDefined();
524
+ });
374
525
 
375
- const serviceSpec = await generateServiceSpec(form);
526
+ it("genera scaling configuration", async () => {
527
+ const form = createMockForm({
528
+ scaling: { cpu: { up: "80", down: "20" }, memory: { up: "80", down: "20" }, instances: { min: 1, max: 5 }, histeresys: "10" },
529
+ });
530
+ const spec = await generateServiceSpec(form);
531
+ expect(spec.deploymentConfig.meta.scaling).toBeDefined();
532
+ expect(spec.deploymentConfig.meta.scaling.simple["svc1"].max_replicas).toBe(5);
533
+ expect(spec.deploymentConfig.meta.scaling.simple["svc1"].hysteresis).toBe(10);
534
+ });
376
535
 
377
- expect(serviceSpec.deploymentConfig.meta.scaling).toBeDefined();
378
- expect(serviceSpec.deploymentConfig.meta.scaling.simple.servicio1.max_replicas).toBe(5);
536
+ it("sin scaling: meta es objeto vacío", async () => {
537
+ const spec = await generateServiceSpec(createMockForm());
538
+ expect(spec.deploymentConfig.meta).toEqual({});
379
539
  });
380
540
 
381
541
  it("genera spec con marketplace item de tipo service", async () => {
382
542
  const form = createMockForm();
383
- const marketplaceItem = {
384
- type: "service" as const,
543
+ const mktItem = {
544
+ type: "service",
385
545
  domain: "kumori.systems",
386
- module: "marketplace/service",
546
+ module: "marketplace/svc",
387
547
  version: "1.2.3",
388
- serviceName: "MarketplaceService",
389
- deploymentData: { name: "marketplaceRole" },
548
+ serviceName: "MktSvc",
549
+ deploymentData: { name: "mktRole" },
390
550
  roles: ["role1", "role2"],
391
551
  };
552
+ const spec = await generateServiceSpec(form, mktItem as any);
553
+ const role = spec.roles.find((r) => r.name === "mktRole");
554
+ expect(role?.artifact.artifactKind).toBe("service");
555
+ expect(role?.artifact.moduleVersion).toEqual([1, 2, 3]);
556
+ expect(spec.local_components).toEqual({});
557
+ });
392
558
 
393
- const serviceSpec = await generateServiceSpec(form, marketplaceItem as any);
559
+ it("genera spec con marketplace item de tipo component", async () => {
560
+ const form = createMockForm();
561
+ const mktItem = {
562
+ type: "component",
563
+ domain: "kumori.systems",
564
+ module: "marketplace/comp",
565
+ version: "2.0.0",
566
+ serviceName: "MktComp",
567
+ deploymentData: { name: "compRole" },
568
+ roles: [],
569
+ };
570
+ const spec = await generateServiceSpec(form, mktItem as any);
571
+ const role = spec.roles.find((r) => r.name === "compRole");
572
+ expect(role?.artifact.artifactKind).toBe("component");
573
+ expect(spec.local_components).toEqual({});
574
+ });
394
575
 
395
- const marketplaceRole = serviceSpec.roles.find(
396
- (role) => role.name === "marketplaceRole"
576
+ it("genera spec con parámetros de tipo string en roles", async () => {
577
+ const form = createMockForm({
578
+ config: {
579
+ parameters: [{ name: "KEY", value: "val", type: "string" }],
580
+ resources: [],
581
+ },
582
+ });
583
+ const spec = await generateServiceSpec(form);
584
+ const mainRole = spec.roles.find((r) => r.name === "svc1");
585
+ expect(mainRole?.artifact.config.parameters).toContainEqual(
586
+ expect.objectContaining({ name: "KEY", type: "string" }),
397
587
  );
398
- expect(marketplaceRole?.artifact.artifactKind).toBe("service");
399
- expect(serviceSpec.local_components).toEqual({});
588
+ });
589
+
590
+ it("genera spec con parámetros de tipo boolean en roles", async () => {
591
+ const form = createMockForm({
592
+ config: {
593
+ parameters: [{ name: "FLAG", value: "true", type: "boolean" }],
594
+ resources: [],
595
+ },
596
+ });
597
+ const spec = await generateServiceSpec(form);
598
+ const params = spec.deploymentConfig.parameters;
599
+ expect(params).toContainEqual(expect.objectContaining({ name: "FLAG", type: "bool" }));
600
+ });
601
+
602
+ it("genera spec con parámetros de tipo number en deploymentConfig", async () => {
603
+ const form = createMockForm({
604
+ config: {
605
+ parameters: [{ name: "COUNT", value: "5", type: "number" }],
606
+ resources: [],
607
+ },
608
+ });
609
+ const spec = await generateServiceSpec(form);
610
+ const params = spec.deploymentConfig.parameters;
611
+ expect(params).toContainEqual(expect.objectContaining({ name: "COUNT", type: "number" }));
612
+ });
613
+
614
+ it("genera spec con parámetros de tipo fileContent en deploymentConfig", async () => {
615
+ const form = createMockForm({
616
+ config: {
617
+ parameters: [{ name: "CFG", value: "v", type: "fileContent", content: "file-content" }],
618
+ resources: [],
619
+ },
620
+ });
621
+ const spec = await generateServiceSpec(form);
622
+ const params = spec.deploymentConfig.parameters;
623
+ expect(params).toContainEqual(expect.objectContaining({ type: "string", value: "file-content" }));
624
+ });
625
+
626
+ it("genera spec con volatile volume en parameters (volatileVolumeResources)", async () => {
627
+ const form = createMockForm({
628
+ config: {
629
+ parameters: [{ name: "tempVol", value: "/tmp", type: "volume", size: 2 }],
630
+ resources: [],
631
+ },
632
+ });
633
+ const spec = await generateServiceSpec(form);
634
+ expect(spec.config.resources).toContainEqual({ volume: { name: "tempVol" } });
635
+ });
636
+
637
+ it("genera spec con marketplaceItem y volatile volumes (llama createVolume)", async () => {
638
+ const { createVolume } = await import("../api/resources-api-service");
639
+ (createVolume as jest.Mock).mockResolvedValue({});
640
+ const form = createMockForm({
641
+ config: {
642
+ parameters: [],
643
+ resources: [{ name: "vol1", type: "volume", size: 3, kind: "volatile" } as any],
644
+ },
645
+ });
646
+ const mktItem = {
647
+ type: "service",
648
+ domain: "kumori",
649
+ module: "mod",
650
+ version: "1.0.0",
651
+ serviceName: "S",
652
+ deploymentData: { name: "r1" },
653
+ roles: [],
654
+ };
655
+ await generateServiceSpec(form, mktItem as any);
656
+ expect(createVolume).toHaveBeenCalled();
657
+ });
658
+
659
+ it("lanza error si createVolume falla con marketplaceItem", async () => {
660
+ const { createVolume } = await import("../api/resources-api-service");
661
+ (createVolume as jest.Mock).mockRejectedValue(new Error("volume-error"));
662
+ const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});
663
+ const form = createMockForm({
664
+ config: {
665
+ parameters: [],
666
+ resources: [{ name: "volFail", type: "volume", size: 3, kind: "volatile" } as any],
667
+ },
668
+ });
669
+ const mktItem: any = { type: "service", domain: "k", module: "m", version: "1.0.0", serviceName: "S", deploymentData: { name: "r" }, roles: [] };
670
+ await expect(generateServiceSpec(form, mktItem)).rejects.toThrow("volume-error");
671
+ consoleSpy.mockRestore();
400
672
  });
401
673
  });
402
674
 
403
- describe("deployService", () => {
675
+ // ── deployServiceHelper ───────────────────────────────────────────────────────
676
+
677
+ describe("deployServiceHelper", () => {
404
678
  const mockClick = jest.fn();
405
-
406
- beforeAll(() => {
407
- (global as any).URL = {
408
- createObjectURL: jest.fn(() => "blob:mock-url"),
409
- };
410
679
 
680
+ beforeAll(() => {
681
+ (global as any).URL = { createObjectURL: jest.fn(() => "blob:mock-url") };
411
682
  (global as any).document = {
412
- createElement: jest.fn(() => ({
413
- click: mockClick,
414
- target: "",
415
- href: "",
416
- })),
683
+ createElement: jest.fn(() => ({ click: mockClick, target: "", href: "" })),
417
684
  };
418
685
  });
419
686
 
@@ -422,33 +689,21 @@ describe("deployService", () => {
422
689
  });
423
690
 
424
691
  const createMockService = (overrides = {}): Service => ({
425
- id: "servicio1",
692
+ id: "s1",
426
693
  tenant: "tenant1",
427
694
  account: "account1",
428
695
  environment: "env1",
429
- name: "Servicio Prueba",
696
+ name: "TestService",
430
697
  logo: "",
431
698
  description: "",
432
699
  revisions: [],
433
- status: {
434
- message: "running",
435
- timestamp: "2024-01-01",
436
- args: [],
437
- code: "200",
438
- },
439
- role: [{ name: "servicio1", instances: [] }],
700
+ status: { message: "running", timestamp: "2024-01-01", args: [], code: "200" },
701
+ role: [{ name: "s1", instances: [] }],
440
702
  links: [],
441
703
  resources: [],
442
704
  parameters: [],
443
705
  usage: {
444
- current: {
445
- cpu: 2,
446
- memory: 512,
447
- storage: 100,
448
- volatileStorage: 10,
449
- nonReplicatedStorage: 10,
450
- persistentStorage: 20,
451
- },
706
+ current: { cpu: 2, memory: 512, storage: 100, volatileStorage: 10, nonReplicatedStorage: 10, persistentStorage: 20 },
452
707
  limit: {
453
708
  cpu: { max: 4, min: 1 },
454
709
  memory: { max: 1024, min: 256 },
@@ -459,14 +714,11 @@ describe("deployService", () => {
459
714
  },
460
715
  cost: 100,
461
716
  },
462
- minReplicas: 1,
463
- maxReplicas: 3,
464
- lastDeployed: "",
465
- project: "project1",
717
+ project: "proj1",
466
718
  registry: "docker.io",
467
- imageName: "miImagen",
468
- entrypoint: "/iniciar",
469
- cmd: "start",
719
+ imageName: "myImage",
720
+ entrypoint: "/start",
721
+ cmd: "run",
470
722
  serverChannels: [],
471
723
  clientChannels: [],
472
724
  duplexChannels: [],
@@ -475,24 +727,18 @@ describe("deployService", () => {
475
727
  });
476
728
 
477
729
  it("retorna FormData con bundle y meta cuando download es false", async () => {
478
- const service = createMockService({ download: false });
479
-
480
- const formData = await deployService(service);
481
-
730
+ const formData = await deployService(createMockService({ download: false }));
482
731
  expect(formData.get("bundle")).toBeTruthy();
483
732
  expect(formData.get("meta")).toBeTruthy();
733
+ expect(formData.get("labels")).toBeTruthy();
484
734
  expect(formData.get("comment")).toBe(" ");
485
-
486
735
  const meta = JSON.parse(formData.get("meta") as string);
487
736
  expect(meta.targetAccount).toBe("account1");
488
737
  expect(meta.targetEnvironment).toBe("env1");
489
738
  });
490
739
 
491
740
  it("retorna FormData vacío y ejecuta descarga cuando download es true", async () => {
492
- const service = createMockService({ download: true });
493
-
494
- const formData = await deployService(service);
495
-
741
+ const formData = await deployService(createMockService({ download: true }));
496
742
  expect((global as any).URL.createObjectURL).toHaveBeenCalled();
497
743
  expect((global as any).document.createElement).toHaveBeenCalledWith("a");
498
744
  expect(mockClick).toHaveBeenCalled();
@@ -500,19 +746,177 @@ describe("deployService", () => {
500
746
  });
501
747
 
502
748
  it("maneja deployment con marketplace item", async () => {
503
- const service = createMockService();
504
- const marketplaceItem = {
505
- type: "service" as const,
749
+ const mktItem = {
750
+ type: "service",
506
751
  domain: "kumori.systems",
507
- module: "marketplace/service",
752
+ module: "marketplace/svc",
508
753
  version: "1.0.0",
509
- serviceName: "TestService",
754
+ serviceName: "TestSvc",
510
755
  deploymentData: { name: "testRole" },
511
756
  roles: ["role1"],
512
757
  };
758
+ const formData = await deployService(createMockService(), mktItem as any);
759
+ expect(formData.get("bundle")).toBeTruthy();
760
+ });
761
+
762
+ it("incluye project en labels cuando está definido", async () => {
763
+ const formData = await deployService(createMockService({ project: "myProject" }));
764
+ const labels = JSON.parse(formData.get("labels") as string);
765
+ expect(labels.project).toBe("myProject");
766
+ });
767
+
768
+ it("maneja servicio con canales HTTPS públicos", async () => {
769
+ const service = createMockService({
770
+ serverChannels: [{ name: "web", protocol: "http", isPublic: true, portNum: 443 }],
771
+ });
772
+ const formData = await deployService(service);
773
+ expect(formData.get("bundle")).toBeTruthy();
774
+ });
775
+
776
+ it("maneja servicio con canales TCP públicos", async () => {
777
+ const service = createMockService({
778
+ serverChannels: [{ name: "tcp1", protocol: "tcp", isPublic: true, portNum: 9000 }],
779
+ });
780
+ const formData = await deployService(service);
781
+ expect(formData.get("bundle")).toBeTruthy();
782
+ });
783
+
784
+ it("maneja servicio con recursos secret y volume", async () => {
785
+ const service = createMockService({
786
+ resources: [
787
+ { name: "sec1", type: "secret", value: "mySecret" },
788
+ { name: "vol1", type: "volume", value: "myVol" },
789
+ ],
790
+ });
791
+ const formData = await deployService(service);
792
+ expect(formData.get("bundle")).toBeTruthy();
793
+ });
794
+
795
+ it("maneja parámetros de tipo boolean en generateServiceSpecDSL", async () => {
796
+ const service = createMockService({
797
+ parameters: [{ name: "FLAG", value: true, type: "boolean" }],
798
+ });
799
+ const formData = await deployService(service);
800
+ expect(formData.get("bundle")).toBeTruthy();
801
+ });
802
+
803
+ it("maneja parámetros de tipo bool en generateServiceSpecDSL", async () => {
804
+ const service = createMockService({
805
+ parameters: [{ name: "FLAG2", value: "true", type: "bool" }],
806
+ });
807
+ const formData = await deployService(service);
808
+ expect(formData.get("bundle")).toBeTruthy();
809
+ });
810
+
811
+ it("maneja parámetros de tipo number en generateServiceSpecDSL", async () => {
812
+ const service = createMockService({
813
+ parameters: [{ name: "COUNT", value: 5, type: "number" }],
814
+ });
815
+ const formData = await deployService(service);
816
+ expect(formData.get("bundle")).toBeTruthy();
817
+ });
818
+
819
+ it("maneja parámetros de tipo file en generateServiceSpecDSL", async () => {
820
+ const service = createMockService({
821
+ parameters: [{ name: "/etc/config.conf", value: "/path/to/file", type: "file" }],
822
+ });
823
+ const formData = await deployService(service);
824
+ expect(formData.get("bundle")).toBeTruthy();
825
+ });
826
+
827
+ it("maneja parámetros de tipo string en generateServiceSpecDSL", async () => {
828
+ const service = createMockService({
829
+ parameters: [{ name: "KEY", value: "val", type: "string" }],
830
+ });
831
+ const formData = await deployService(service);
832
+ expect(formData.get("bundle")).toBeTruthy();
833
+ });
834
+
835
+ it("maneja parámetros de tipo secret en generateServiceSpecDSL", async () => {
836
+ const service = createMockService({
837
+ parameters: [{ name: "SEC", value: "secRef", type: "secret" }],
838
+ });
839
+ const formData = await deployService(service);
840
+ expect(formData.get("bundle")).toBeTruthy();
841
+ });
842
+
843
+ it("maneja resources de tipo volume sin size en generateServiceSpecDSL", async () => {
844
+ const service = createMockService({
845
+ resources: [{ name: "vol1", type: "volume", value: "existing-vol" }],
846
+ });
847
+ const formData = await deployService(service);
848
+ expect(formData.get("bundle")).toBeTruthy();
849
+ });
850
+
851
+ it("maneja resources de tipo volume con size persistent en generateServiceSpecDSL", async () => {
852
+ const service = createMockService({
853
+ resources: [{ name: "vol1", type: "volume", maxItems: 10, kind: "persistent" } as any],
854
+ });
855
+ const formData = await deployService(service);
856
+ expect(formData.get("bundle")).toBeTruthy();
857
+ });
513
858
 
514
- const formData = await deployService(service, marketplaceItem as any);
859
+ it("maneja resources de tipo volume con size volatile en generateServiceSpecDSL", async () => {
860
+ const service = createMockService({
861
+ resources: [{ name: "vol2", type: "volume", maxItems: 5, kind: "volatile" } as any],
862
+ });
863
+ const formData = await deployService(service);
864
+ expect(formData.get("bundle")).toBeTruthy();
865
+ });
866
+
867
+ it("maneja canal HTTPS público sin certificateResource en generateServiceSpecDSL", async () => {
868
+ const service = createMockService({
869
+ serverChannels: [{ name: "web", protocol: "http", isPublic: true, portNum: 443 }],
870
+ });
871
+ const formData = await deployService(service);
872
+ expect(formData.get("bundle")).toBeTruthy();
873
+ });
874
+
875
+ it("maneja canal HTTPS público con certificateResource en generateServiceSpecDSL", async () => {
876
+ const service = createMockService({
877
+ serverChannels: [{ name: "web", protocol: "http", isPublic: true, certificateResource: "my-cert" }],
878
+ });
879
+ const formData = await deployService(service);
880
+ expect(formData.get("bundle")).toBeTruthy();
881
+ });
515
882
 
883
+ it("maneja canal HTTPS con mTLS en generateServiceSpecDSL", async () => {
884
+ const service = createMockService({
885
+ serverChannels: [{ name: "web", protocol: "http", isPublic: true, withMtls: true, caResource: "my-ca", certificateResource: "my-cert" }],
886
+ });
887
+ const formData = await deployService(service);
516
888
  expect(formData.get("bundle")).toBeTruthy();
517
889
  });
518
- });
890
+
891
+ it("maneja canal TCP público en generateServiceSpecDSL", async () => {
892
+ const service = createMockService({
893
+ serverChannels: [{ name: "tcp1", protocol: "tcp", isPublic: true }],
894
+ });
895
+ const formData = await deployService(service);
896
+ expect(formData.get("bundle")).toBeTruthy();
897
+ });
898
+
899
+ it("maneja clientChannels en generateServiceSpecDSL", async () => {
900
+ const service = createMockService({
901
+ clientChannels: [{ name: "clientCh" }],
902
+ });
903
+ const formData = await deployService(service);
904
+ expect(formData.get("bundle")).toBeTruthy();
905
+ });
906
+
907
+ it("maneja marketplace item con package (hasMarketplacePackage)", async () => {
908
+ const mktItem = {
909
+ type: "service",
910
+ domain: "kumori.systems",
911
+ module: "marketplace/svc",
912
+ version: "2.1.0",
913
+ serviceName: "PkgSvc",
914
+ artifact: "my-artifact",
915
+ package: "s3://my-bucket/pkg.zip",
916
+ deploymentData: { name: "pkgRole" },
917
+ roles: ["role1"],
918
+ };
919
+ const formData = await deployService(createMockService(), mktItem as any);
920
+ expect(formData.get("bundle")).toBeTruthy();
921
+ });
922
+ });