import { Service } from "@kumori/aurora-interfaces"; import { withDefaultValue, handleParametersToGenerateData, transformServiceToForm, generateServiceSpec, deployServiceHelper as deployService, ServiceSpecForm, } from "../api/deploy-service-helper"; jest.mock("@kumori/kumori-dsl-generator", () => ({ buildServiceDeploymentModule: jest.fn().mockResolvedValue(new Blob(["bundleDSL"])), })); jest.mock("@kumori/kumori-module-generator", () => ({ buildServiceDeploymentModuleWithLocalComponent: jest .fn() .mockResolvedValue(new Blob(["bundleFalso"])), })); jest.mock("../api/resources-api-service", () => ({ createVolume: jest.fn().mockResolvedValue({}), })); jest.mock("../websocket-manager", () => ({ getReferenceDomain: jest.fn().mockReturnValue("test-domain.com"), })); // ── withDefaultValue ───────────────────────────────────────────────────────── describe("withDefaultValue", () => { it("retorna el valor si no es null ni undefined", () => { expect(withDefaultValue("valor", "default")).toBe("valor"); expect(withDefaultValue(0, 10)).toBe(0); expect(withDefaultValue(false, true)).toBe(false); expect(withDefaultValue("", "default")).toBe(""); }); it("retorna el valor por defecto si el valor es null o undefined", () => { expect(withDefaultValue(null, "default")).toBe("default"); expect(withDefaultValue(undefined, "default")).toBe("default"); }); }); // ── handleParametersToGenerateData ─────────────────────────────────────────── describe("handleParametersToGenerateData", () => { it("maneja tipo string", () => { const result = handleParametersToGenerateData( [{ name: "p1", value: "v1", type: "string" }], [], ); expect(result.parameters).toContainEqual({ name: "p1", type: "string", defaultValue: "v1" }); expect(result.environment).toContainEqual({ varName: "p1", param: "p1" }); }); it("maneja tipo number", () => { const result = handleParametersToGenerateData( [{ name: "p1", value: "42", type: "number" }], [], ); expect(result.parameters[0].defaultValue).toBe(42); expect(result.environment).toContainEqual({ varName: "p1", param: "p1" }); }); it("maneja tipo boolean con string 'true'", () => { const result = handleParametersToGenerateData( [{ name: "p1", value: "true", type: "boolean" }], [], ); expect(result.parameters[0].defaultValue).toBe(true); }); it("maneja tipo bool con string 'false'", () => { const result = handleParametersToGenerateData( [{ name: "p1", value: "false", type: "bool" }], [], ); expect(result.parameters[0].defaultValue).toBe(false); }); it("maneja tipo bool con valor boolean true", () => { const result = handleParametersToGenerateData( [{ name: "p1", value: true as any, type: "bool" }], [], ); expect(result.parameters[0].defaultValue).toBe(true); }); it("maneja tipo secret", () => { const result = handleParametersToGenerateData( [{ name: "sec1", value: "secretValue", type: "secret" }], [], ); expect(result.environment).toContainEqual({ varName: "sec1", secret: "sec1" }); expect(result.resources).toContainEqual({ secret: { name: "secretValue" } }); expect(result.parameters).toHaveLength(0); }); it("maneja tipo file", () => { const result = handleParametersToGenerateData( [{ name: "myconfig", value: "/path/to/file", type: "file" }], [], ); expect(result.parameters[0].name).toBe("CONFIG_FILE_0"); expect(result.parameters[0].type).toBe("string"); expect(result.parameters[0].defaultValue).toBe("/path/to/file"); expect(result.fileSystem).toContainEqual({ path: "myconfig", param: "CONFIG_FILE_0" }); }); it("maneja tipo fileContent con content", () => { const result = handleParametersToGenerateData( [{ name: "f1", value: "/custom/path", type: "fileContent", content: "contenido" }], [], ); expect(result.parameters[0].defaultValue).toBe("contenido"); expect(result.fileSystem[0].path).toBe("/custom/path"); expect(result.fileSystem[0].param).toBe("CONFIG_FILE_0"); }); it("maneja tipo fileContent sin content (usa value)", () => { const result = handleParametersToGenerateData( [{ name: "f1", value: "/some/path", type: "fileContent" }], [], ); expect(result.parameters[0].defaultValue).toBe("/some/path"); expect(result.fileSystem[0].path).toBe("/some/path"); }); it("maneja tipo volume sin content (usa value)", () => { const result = handleParametersToGenerateData( [{ name: "vol1", value: "/data", type: "volume" }], [], ); expect(result.resources).toContainEqual({ volume: { name: "vol1" } }); expect(result.fileSystem).toContainEqual({ path: "/data", resourceVolume: "vol1" }); }); it("maneja tipo volume con content", () => { const result = handleParametersToGenerateData( [{ name: "vol1", value: "/data", type: "volume", content: "/mount/path" }], [], ); expect(result.resources).toContainEqual({ volume: { name: "vol1" } }); expect(result.fileSystem).toContainEqual({ path: "/mount/path", resourceVolume: "vol1" }); }); it("maneja tipo serviceUrl", () => { const result = handleParametersToGenerateData( [{ name: "url1", value: "https://api.example.com", type: "serviceUrl" }], [], ); expect(result.parameters[0].defaultValue).toBe("https://api.example.com"); expect(result.environment).toContainEqual({ varName: "url1", param: "url1" }); }); it("maneja tipos desconocidos con warn", () => { const consoleSpy = jest.spyOn(console, "warn").mockImplementation(() => {}); const result = handleParametersToGenerateData( [{ name: "p1", value: "v1", type: "unknown" }], [], ); expect(consoleSpy).toHaveBeenCalledWith("Unknown parameter type: unknown"); expect(result.parameters[0].type).toBe("string"); consoleSpy.mockRestore(); }); it("maneja tipos desconocidos con value undefined", () => { const consoleSpy = jest.spyOn(console, "warn").mockImplementation(() => {}); handleParametersToGenerateData( [{ name: "p1", value: undefined as any, type: "unknown" }], [], ); expect(consoleSpy).toHaveBeenCalled(); consoleSpy.mockRestore(); }); it("maneja formResources de tipo secret", () => { const result = handleParametersToGenerateData([], [ { name: "res1", type: "secret", value: "secretValue" }, ]); expect(result.resources).toContainEqual({ secret: { name: "res1" } }); expect(result.environment).toContainEqual({ varName: "res1", secret: "res1" }); }); it("maneja formResources de tipo volume", () => { const result = handleParametersToGenerateData([], [ { name: "vol1", type: "volume", key: "/mnt/data" }, ]); expect(result.resources).toContainEqual({ volume: { name: "vol1" } }); expect(result.fileSystem).toContainEqual({ path: "/mnt/data", resourceVolume: "vol1" }); }); it("maneja formResources de tipo volume sin key", () => { const result = handleParametersToGenerateData([], [ { name: "vol1", type: "volume" }, ]); expect(result.fileSystem[0].path).toBe(""); }); it("maneja formResources de tipo desconocido con warn", () => { const consoleSpy = jest.spyOn(console, "warn").mockImplementation(() => {}); handleParametersToGenerateData([], [{ name: "r1", type: "unknown" }]); expect(consoleSpy).toHaveBeenCalledWith("Unknown resource type: unknown"); consoleSpy.mockRestore(); }); it("maneja múltiples parámetros a la vez", () => { const result = handleParametersToGenerateData( [ { name: "s1", value: "v1", type: "string" }, { name: "n1", value: "10", type: "number" }, { name: "b1", value: "true", type: "boolean" }, ], [], ); expect(result.parameters).toHaveLength(3); expect(result.environment).toHaveLength(3); }); }); // ── transformServiceToForm ──────────────────────────────────────────────────── describe("transformServiceToForm", () => { const createMockService = (overrides = {}): Service => ({ id: "s1", tenant: "tenant1", account: "account1", environment: "env1", name: "TestService", logo: "", description: "", revisions: [], status: { message: "running", timestamp: "2024-01-01", args: [], code: "200" }, role: [{ name: "s1", instances: [] }], links: [], resources: [], parameters: [], usage: { current: { cpu: 2, memory: 512, storage: 100, volatileStorage: 10, nonReplicatedStorage: 10, persistentStorage: 20 }, limit: { cpu: { max: 4, min: 1 }, memory: { max: 1024, min: 256 }, storage: { max: 200, min: 50 }, volatileStorage: { max: 20, min: 5 }, nonReplicatedStorage: { max: 20, min: 5 }, persistentStorage: { max: 50, min: 10 }, }, cost: 100, }, project: "proj1", registry: "docker.io", imageName: "myImage", entrypoint: "/start", cmd: "run", serverChannels: [], clientChannels: [], duplexChannels: [], cloudProvider: "aws", ...overrides, }); it("transforma servicio básico correctamente", () => { const form = transformServiceToForm(createMockService()); expect(form.tenantId).toBe("tenant1"); expect(form.accountId).toBe("account1"); expect(form.environmentId).toBe("env1"); expect(form.serviceId).toBe("TestService"); expect(form.cpuRequirements).toBe(4); expect(form.memoryRequirements).toBe(1024); expect(form.registryUrl).toBe("docker.io"); expect(form.imageTag).toBe("myImage"); expect(form.defaultExecutable).toEqual({ cmd: "run", entryPoint: "/start" }); expect(form.clientChannels).toEqual([]); expect(form.channels).toEqual([]); expect(form.clientChannelsExtra).toEqual([]); }); it("transforma serverChannels con protocolo http a HTTPS", () => { const service = createMockService({ serverChannels: [{ name: "web", port: 8080, isPublic: true, protocol: "http", portNum: 80 }], }); const form = transformServiceToForm(service); expect(form.channels[0].protocol).toBe("HTTPS"); expect(form.channels[0].containerPort).toBe(8080); expect(form.channels[0].isPublic).toBe(true); expect(form.channels[0].publicPort).toBe("80"); }); it("transforma serverChannels con protocolo tcp a TCP", () => { const service = createMockService({ serverChannels: [{ name: "tcp1", protocol: "tcp" }], }); const form = transformServiceToForm(service); expect(form.channels[0].protocol).toBe("TCP"); }); it("transforma serverChannels con protocolo desconocido a TCP", () => { const service = createMockService({ serverChannels: [{ name: "other", protocol: "grpc" }], }); const form = transformServiceToForm(service); expect(form.channels[0].protocol).toBe("TCP"); }); it("transforma clientChannels y duplexChannels", () => { const service = createMockService({ clientChannels: [{ name: "clientCh" }], duplexChannels: [{ name: "duplexCh" }], }); const form = transformServiceToForm(service); expect(form.clientChannels).toEqual([{ name: "clientCh" }]); expect(form.clientChannelsExtra).toEqual([{ name: "duplexCh" }]); }); it("transforma resources de tipo volume al fileSystem", () => { const service = createMockService({ resources: [ { name: "vol1", type: "volume", key: "/data" }, { name: "sec1", type: "secret", value: "mySecret" }, ], }); const form = transformServiceToForm(service); expect(form.fileSystem).toContainEqual({ path: "/data", resourceVolume: "vol1" }); expect(form.config.resources).toHaveLength(2); }); it("transforma parameters", () => { const service = createMockService({ parameters: [{ name: "env", value: "prod", type: "string" }], }); const form = transformServiceToForm(service); expect(form.config.parameters).toContainEqual({ name: "env", value: "prod", type: "string" }); }); it("transforma scaling configuration", () => { const service = createMockService({ role: [{ name: "s1", instances: [], scalling: { cpu: { up: "80", down: "20" }, memory: { up: "80", down: "20" }, instances: { max: 5, min: 1 }, histeresys: "10" }, }], }); const form = transformServiceToForm(service); expect(form.scaling).toBeDefined(); expect(form.scaling?.instances.max).toBe(5); }); it("transforma certificateResource, withMtls y caResource en channels", () => { const service = createMockService({ serverChannels: [{ name: "web", protocol: "http", certificateResource: "mycert", withMtls: true, caResource: "myca" }], }); const form = transformServiceToForm(service); expect(form.channels[0].certificateResource).toBe("mycert"); expect(form.channels[0].withMtls).toBe(true); expect(form.channels[0].caResource).toBe("myca"); }); }); // ── generateServiceSpec ─────────────────────────────────────────────────────── describe("generateServiceSpec", () => { const createMockForm = (overrides: Partial = {}): ServiceSpecForm => ({ tenantId: "tenant1", accountId: "account1", environmentId: "env1", serviceId: "svc1", cpuRequirements: 2, memoryRequirements: 512, registryUrl: "docker.io", imageTag: "myImage", clientChannels: [], channels: [], defaultExecutable: {}, config: { parameters: [], resources: [] }, ...overrides, }); it("genera spec básico sin channels ni recursos", async () => { const spec = await generateServiceSpec(createMockForm()); expect(spec.type).toBe("service"); expect(spec.subtype).toBe("service_with_local_component"); expect(spec.tenantId).toBe("tenant1"); expect(spec.deploymentConfig.scale.detail["svc1"]).toBe(1); expect(spec.local_components["svc1"]).toBeDefined(); }); it("genera spec con clientChannels", async () => { const spec = await generateServiceSpec(createMockForm({ clientChannels: [{ name: "ch1" }] })); expect(spec.local_components["svc1"].channels.client).toContain("ch1"); expect(spec.topology).toContainEqual( expect.objectContaining({ clientRole: "svc1", clientChannel: "ch1", serverRole: "self" }), ); }); it("genera inbound role para canal HTTPS público sin certificateResource (cert por defecto)", async () => { const form = createMockForm({ channels: [{ channelName: "web", containerPort: 443, isPublic: true, protocol: "HTTPS", domain: "example.com" }], }); const spec = await generateServiceSpec(form); const inbound = spec.roles.find((r) => r.name === "web_inbound"); expect(inbound).toBeDefined(); expect(inbound?.artifact.config.parameters).toContainEqual({ name: "type", value: "https", type: "string" }); expect(spec.config.resources).toContainEqual({ certificate: { name: "main_inbound_servercert" } }); const certDeploy = spec.deploymentConfig.resources.find((r: any) => r.certificate?.name === "main_inbound_servercert"); expect(certDeploy).toBeDefined(); }); it("genera inbound role para canal HTTPS público con certificateResource personalizado", async () => { const form = createMockForm({ channels: [{ channelName: "web", containerPort: 443, isPublic: true, protocol: "HTTPS", domain: "example.com", certificateResource: "my-cert" }], }); const spec = await generateServiceSpec(form); const certConfig = spec.config.resources.find((r: any) => r.certificate?.name === "web_cert"); expect(certConfig).toBeDefined(); const certDeploy = spec.deploymentConfig.resources.find((r: any) => r.certificate?.name === "web_cert"); expect(certDeploy).toBeDefined(); expect((certDeploy as any).certificate.resource).toBe("my-cert"); }); it("genera inbound role para canal HTTPS con mTLS y caResource", async () => { const form = createMockForm({ channels: [{ channelName: "web", containerPort: 443, isPublic: true, protocol: "HTTPS", domain: "example.com", certificateResource: "my-cert", withMtls: true, caResource: "my-ca", }], }); const spec = await generateServiceSpec(form); const caConfig = spec.config.resources.find((r: any) => r.ca?.name === "web_ca"); expect(caConfig).toBeDefined(); const caDeploy = spec.deploymentConfig.resources.find((r: any) => r.ca?.name === "web_ca"); expect(caDeploy).toBeDefined(); }); it("genera inbound role para canal TCP público", async () => { const form = createMockForm({ channels: [{ channelName: "tcp1", containerPort: 9000, isPublic: true, protocol: "TCP", publicPort: "9000" }], }); const spec = await generateServiceSpec(form); const inbound = spec.roles.find((r) => r.name === "tcp1_inbound"); expect(inbound).toBeDefined(); expect(inbound?.artifact.config.parameters).toContainEqual({ name: "type", value: "tcp", type: "string" }); const portDeploy = spec.deploymentConfig.resources.find((r: any) => r.port?.name === "tcp1_port"); expect(portDeploy).toBeDefined(); }); it("canal con containerPort 0 no añade port en channels.server", async () => { const form = createMockForm({ channels: [{ channelName: "web", containerPort: 0, isPublic: false, protocol: "HTTPS" }], }); const spec = await generateServiceSpec(form); const ch = spec.channels.server.find((s: any) => s.name === "web"); expect(ch).toEqual({ name: "web" }); }); it("genera topology para canal público", async () => { const form = createMockForm({ channels: [{ channelName: "web", containerPort: 443, isPublic: true, protocol: "HTTPS" }], }); const spec = await generateServiceSpec(form); expect(spec.topology).toContainEqual( expect.objectContaining({ clientRole: "web_inbound", clientChannel: "inbound" }), ); expect(spec.topology).toContainEqual( expect.objectContaining({ clientRole: "self", clientChannel: "web" }), ); }); it("maneja recursos de tipo secret en config", async () => { const form = createMockForm({ config: { parameters: [], resources: [{ name: "sec1", type: "secret", value: "my-secret" }] }, }); const spec = await generateServiceSpec(form); expect(spec.config.resources).toContainEqual({ secret: { name: "sec1" } }); const secDeploy = spec.deploymentConfig.resources.find((r: any) => r.secret?.name === "sec1"); expect(secDeploy).toBeDefined(); }); it("maneja volumen sin size (referencia externa)", async () => { const form = createMockForm({ config: { parameters: [], resources: [{ name: "vol1", type: "volume", value: "existing-vol" }] }, }); const spec = await generateServiceSpec(form); const volDeploy = spec.deploymentConfig.resources.find((r: any) => r.volume?.name === "vol1"); expect(volDeploy).toBeDefined(); expect((volDeploy as any).volume.resource).toBe("existing-vol"); }); it("maneja volúmenes con type volatile", async () => { const form = createMockForm({ config: { parameters: [], resources: [{ name: "vol1", type: "volume", size: 5, kind: "volatile" } as any] }, }); const spec = await generateServiceSpec(form); const volDeploy = spec.deploymentConfig.resources.find((r: any) => r.volume?.volume?.type === "volatile"); expect(volDeploy).toBeDefined(); expect((volDeploy as any).volume.volume.size).toBe(5000); }); it("maneja volúmenes con type persistent", async () => { const form = createMockForm({ config: { parameters: [], resources: [{ name: "vol2", type: "volume", size: 10, kind: "persistent" } as any] }, }); const spec = await generateServiceSpec(form); const volDeploy = spec.deploymentConfig.resources.find((r: any) => r.volume?.volume?.type === "persistent"); expect(volDeploy).toBeDefined(); }); it("maneja volúmenes con type nonreplicated (default)", async () => { const form = createMockForm({ config: { parameters: [], resources: [{ name: "vol3", type: "volume", size: 15, kind: "nonreplicated" } as any] }, }); const spec = await generateServiceSpec(form); const volDeploy = spec.deploymentConfig.resources.find((r: any) => r.volume?.volume?.type === "nonreplicated"); expect(volDeploy).toBeDefined(); }); it("maneja volúmenes sin kind (usa nonreplicated por defecto)", async () => { const form = createMockForm({ config: { parameters: [], resources: [{ name: "vol4", type: "volume", size: 20 } as any] }, }); const spec = await generateServiceSpec(form); const volDeploy = spec.deploymentConfig.resources.find((r: any) => r.volume?.volume?.type === "nonreplicated"); expect(volDeploy).toBeDefined(); }); it("genera scaling configuration", async () => { const form = createMockForm({ scaling: { cpu: { up: "80", down: "20" }, memory: { up: "80", down: "20" }, instances: { min: 1, max: 5 }, histeresys: "10" }, }); const spec = await generateServiceSpec(form); expect(spec.deploymentConfig.meta.scaling).toBeDefined(); expect(spec.deploymentConfig.meta.scaling.simple["svc1"].max_replicas).toBe(5); expect(spec.deploymentConfig.meta.scaling.simple["svc1"].hysteresis).toBe(10); }); it("sin scaling: meta es objeto vacío", async () => { const spec = await generateServiceSpec(createMockForm()); expect(spec.deploymentConfig.meta).toEqual({}); }); it("genera spec con marketplace item de tipo service", async () => { const form = createMockForm(); const mktItem = { type: "service", domain: "kumori.systems", module: "marketplace/svc", version: "1.2.3", serviceName: "MktSvc", deploymentData: { name: "mktRole" }, roles: ["role1", "role2"], }; const spec = await generateServiceSpec(form, mktItem as any); const role = spec.roles.find((r) => r.name === "mktRole"); expect(role?.artifact.artifactKind).toBe("service"); expect(role?.artifact.moduleVersion).toEqual([1, 2, 3]); expect(spec.local_components).toEqual({}); }); it("genera spec con marketplace item de tipo component", async () => { const form = createMockForm(); const mktItem = { type: "component", domain: "kumori.systems", module: "marketplace/comp", version: "2.0.0", serviceName: "MktComp", deploymentData: { name: "compRole" }, roles: [], }; const spec = await generateServiceSpec(form, mktItem as any); const role = spec.roles.find((r) => r.name === "compRole"); expect(role?.artifact.artifactKind).toBe("component"); expect(spec.local_components).toEqual({}); }); it("genera spec con parámetros de tipo string en roles", async () => { const form = createMockForm({ config: { parameters: [{ name: "KEY", value: "val", type: "string" }], resources: [], }, }); const spec = await generateServiceSpec(form); const mainRole = spec.roles.find((r) => r.name === "svc1"); expect(mainRole?.artifact.config.parameters).toContainEqual( expect.objectContaining({ name: "KEY", type: "string" }), ); }); it("genera spec con parámetros de tipo boolean en roles", async () => { const form = createMockForm({ config: { parameters: [{ name: "FLAG", value: "true", type: "boolean" }], resources: [], }, }); const spec = await generateServiceSpec(form); const params = spec.deploymentConfig.parameters; expect(params).toContainEqual(expect.objectContaining({ name: "FLAG", type: "bool" })); }); it("genera spec con parámetros de tipo number en deploymentConfig", async () => { const form = createMockForm({ config: { parameters: [{ name: "COUNT", value: "5", type: "number" }], resources: [], }, }); const spec = await generateServiceSpec(form); const params = spec.deploymentConfig.parameters; expect(params).toContainEqual(expect.objectContaining({ name: "COUNT", type: "number" })); }); it("genera spec con parámetros de tipo fileContent en deploymentConfig", async () => { const form = createMockForm({ config: { parameters: [{ name: "CFG", value: "v", type: "fileContent", content: "file-content" }], resources: [], }, }); const spec = await generateServiceSpec(form); const params = spec.deploymentConfig.parameters; expect(params).toContainEqual(expect.objectContaining({ type: "string", value: "file-content" })); }); it("genera spec con volatile volume en parameters (volatileVolumeResources)", async () => { const form = createMockForm({ config: { parameters: [{ name: "tempVol", value: "/tmp", type: "volume", size: 2 }], resources: [], }, }); const spec = await generateServiceSpec(form); expect(spec.config.resources).toContainEqual({ volume: { name: "tempVol" } }); }); it("genera spec con marketplaceItem y volatile volumes (llama createVolume)", async () => { const { createVolume } = await import("../api/resources-api-service"); (createVolume as jest.Mock).mockResolvedValue({}); const form = createMockForm({ config: { parameters: [], resources: [{ name: "vol1", type: "volume", size: 3, kind: "volatile" } as any], }, }); const mktItem = { type: "service", domain: "kumori", module: "mod", version: "1.0.0", serviceName: "S", deploymentData: { name: "r1" }, roles: [], }; await generateServiceSpec(form, mktItem as any); expect(createVolume).toHaveBeenCalled(); }); it("lanza error si createVolume falla con marketplaceItem", async () => { const { createVolume } = await import("../api/resources-api-service"); (createVolume as jest.Mock).mockRejectedValue(new Error("volume-error")); const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {}); const form = createMockForm({ config: { parameters: [], resources: [{ name: "volFail", type: "volume", size: 3, kind: "volatile" } as any], }, }); const mktItem: any = { type: "service", domain: "k", module: "m", version: "1.0.0", serviceName: "S", deploymentData: { name: "r" }, roles: [] }; await expect(generateServiceSpec(form, mktItem)).rejects.toThrow("volume-error"); consoleSpy.mockRestore(); }); }); // ── deployServiceHelper ─────────────────────────────────────────────────────── describe("deployServiceHelper", () => { const mockClick = jest.fn(); beforeAll(() => { (global as any).URL = { createObjectURL: jest.fn(() => "blob:mock-url") }; (global as any).document = { createElement: jest.fn(() => ({ click: mockClick, target: "", href: "" })), }; }); afterEach(() => { jest.clearAllMocks(); }); const createMockService = (overrides = {}): Service => ({ id: "s1", tenant: "tenant1", account: "account1", environment: "env1", name: "TestService", logo: "", description: "", revisions: [], status: { message: "running", timestamp: "2024-01-01", args: [], code: "200" }, role: [{ name: "s1", instances: [] }], links: [], resources: [], parameters: [], usage: { current: { cpu: 2, memory: 512, storage: 100, volatileStorage: 10, nonReplicatedStorage: 10, persistentStorage: 20 }, limit: { cpu: { max: 4, min: 1 }, memory: { max: 1024, min: 256 }, storage: { max: 200, min: 50 }, volatileStorage: { max: 20, min: 5 }, nonReplicatedStorage: { max: 20, min: 5 }, persistentStorage: { max: 50, min: 10 }, }, cost: 100, }, project: "proj1", registry: "docker.io", imageName: "myImage", entrypoint: "/start", cmd: "run", serverChannels: [], clientChannels: [], duplexChannels: [], cloudProvider: "aws", ...overrides, }); it("retorna FormData con bundle y meta cuando download es false", async () => { const formData = await deployService(createMockService({ download: false })); expect(formData.get("bundle")).toBeTruthy(); expect(formData.get("meta")).toBeTruthy(); expect(formData.get("labels")).toBeTruthy(); expect(formData.get("comment")).toBe(" "); const meta = JSON.parse(formData.get("meta") as string); expect(meta.targetAccount).toBe("account1"); expect(meta.targetEnvironment).toBe("env1"); }); it("retorna FormData vacío y ejecuta descarga cuando download es true", async () => { const formData = await deployService(createMockService({ download: true })); expect((global as any).URL.createObjectURL).toHaveBeenCalled(); expect((global as any).document.createElement).toHaveBeenCalledWith("a"); expect(mockClick).toHaveBeenCalled(); expect(Array.from(formData.keys()).length).toBe(0); }); it("maneja deployment con marketplace item", async () => { const mktItem = { type: "service", domain: "kumori.systems", module: "marketplace/svc", version: "1.0.0", serviceName: "TestSvc", deploymentData: { name: "testRole" }, roles: ["role1"], }; const formData = await deployService(createMockService(), mktItem as any); expect(formData.get("bundle")).toBeTruthy(); }); it("incluye project en labels cuando está definido", async () => { const formData = await deployService(createMockService({ project: "myProject" })); const labels = JSON.parse(formData.get("labels") as string); expect(labels.project).toBe("myProject"); }); it("maneja servicio con canales HTTPS públicos", async () => { const service = createMockService({ serverChannels: [{ name: "web", protocol: "http", isPublic: true, portNum: 443 }], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja servicio con canales TCP públicos", async () => { const service = createMockService({ serverChannels: [{ name: "tcp1", protocol: "tcp", isPublic: true, portNum: 9000 }], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja servicio con recursos secret y volume", async () => { const service = createMockService({ resources: [ { name: "sec1", type: "secret", value: "mySecret" }, { name: "vol1", type: "volume", value: "myVol" }, ], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja parámetros de tipo boolean en generateServiceSpecDSL", async () => { const service = createMockService({ parameters: [{ name: "FLAG", value: true, type: "boolean" }], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja parámetros de tipo bool en generateServiceSpecDSL", async () => { const service = createMockService({ parameters: [{ name: "FLAG2", value: "true", type: "bool" }], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja parámetros de tipo number en generateServiceSpecDSL", async () => { const service = createMockService({ parameters: [{ name: "COUNT", value: 5, type: "number" }], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja parámetros de tipo file en generateServiceSpecDSL", async () => { const service = createMockService({ parameters: [{ name: "/etc/config.conf", value: "/path/to/file", type: "file" }], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja parámetros de tipo string en generateServiceSpecDSL", async () => { const service = createMockService({ parameters: [{ name: "KEY", value: "val", type: "string" }], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja parámetros de tipo secret en generateServiceSpecDSL", async () => { const service = createMockService({ parameters: [{ name: "SEC", value: "secRef", type: "secret" }], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja resources de tipo volume sin size en generateServiceSpecDSL", async () => { const service = createMockService({ resources: [{ name: "vol1", type: "volume", value: "existing-vol" }], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja resources de tipo volume con size persistent en generateServiceSpecDSL", async () => { const service = createMockService({ resources: [{ name: "vol1", type: "volume", maxItems: 10, kind: "persistent" } as any], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja resources de tipo volume con size volatile en generateServiceSpecDSL", async () => { const service = createMockService({ resources: [{ name: "vol2", type: "volume", maxItems: 5, kind: "volatile" } as any], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja canal HTTPS público sin certificateResource en generateServiceSpecDSL", async () => { const service = createMockService({ serverChannels: [{ name: "web", protocol: "http", isPublic: true, portNum: 443 }], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja canal HTTPS público con certificateResource en generateServiceSpecDSL", async () => { const service = createMockService({ serverChannels: [{ name: "web", protocol: "http", isPublic: true, certificateResource: "my-cert" }], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja canal HTTPS con mTLS en generateServiceSpecDSL", async () => { const service = createMockService({ serverChannels: [{ name: "web", protocol: "http", isPublic: true, withMtls: true, caResource: "my-ca", certificateResource: "my-cert" }], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja canal TCP público en generateServiceSpecDSL", async () => { const service = createMockService({ serverChannels: [{ name: "tcp1", protocol: "tcp", isPublic: true }], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja clientChannels en generateServiceSpecDSL", async () => { const service = createMockService({ clientChannels: [{ name: "clientCh" }], }); const formData = await deployService(service); expect(formData.get("bundle")).toBeTruthy(); }); it("maneja marketplace item con package (hasMarketplacePackage)", async () => { const mktItem = { type: "service", domain: "kumori.systems", module: "marketplace/svc", version: "2.1.0", serviceName: "PkgSvc", artifact: "my-artifact", package: "s3://my-bucket/pkg.zip", deploymentData: { name: "pkgRole" }, roles: ["role1"], }; const formData = await deployService(createMockService(), mktItem as any); expect(formData.get("bundle")).toBeTruthy(); }); });