UNPKG

@embeddable.com/sdk-core

Version:

Core Embeddable SDK module responsible for web-components bundling and publishing.

897 lines (763 loc) 28.4 kB
import push, { buildArchive, EMBEDDABLE_FILES, sendBuild, sendBuildByApiKey } from "./push"; import provideConfig from "./provideConfig"; import { fileFromPath } from "formdata-node/file-from-path"; import * as path from "path"; import yazl from "yazl"; import * as fs from "node:fs/promises"; import * as fsSync from "node:fs"; import { findFiles } from "@embeddable.com/sdk-utils"; import { ResolvedEmbeddableConfig } from "./defineConfig"; import { vi, describe, it, expect, beforeEach } from "vitest"; import { checkBuildSuccess, checkNodeVersion, getArgumentByKey, shouldSkipModelCheck } from "./utils"; // @ts-ignore import reportErrorToRollbar from "./rollbar.mjs"; import { server } from "../../../mocks/server"; import { http, HttpResponse } from "msw"; const warnMock = vi.fn(); const infoMock = { info: vi.fn(), succeed: vi.fn(), fail: vi.fn(), }; const startMock = { succeed: vi.fn(), info: () => infoMock, fail: vi.fn(), }; vi.mock("ora", () => ({ default: () => ({ start: vi.fn().mockReturnValue(startMock), info: vi.fn(), warn: warnMock, }), })); vi.mock("./utils", () => ({ checkNodeVersion: vi.fn(), checkBuildSuccess: vi.fn(), getArgumentByKey: vi.fn(), getSDKVersions: vi.fn(), shouldSkipModelCheck: vi.fn().mockReturnValue(false), })); vi.mock("node:fs/promises", () => ({ writeFile: vi.fn(), readFile: vi.fn(), access: vi.fn(), rm: vi.fn(), mkdir: vi.fn(), stat: vi.fn(), appendFile: vi.fn(), })); vi.mock("node:fs", () => ({ createWriteStream: vi.fn(), readdirSync: vi.fn().mockReturnValue([]), statSync: vi.fn().mockReturnValue({ isFile: () => true }), existsSync: vi.fn().mockReturnValue(true), })); vi.mock("./provideConfig", () => ({ default: vi.fn().mockResolvedValue({ client: { rootDir: "rootDir", buildDir: "buildDir", archiveFile: "embeddable-build.zip", }, }), })); vi.mock("./rollbar.mjs", () => ({ default: vi.fn(), })); vi.mock("@embeddable.com/sdk-utils", () => ({ findFiles: vi.fn(), })); const { zipRef } = vi.hoisted(() => ({ zipRef: { current: null as any }, })); vi.mock("yazl", () => ({ default: { ZipFile: vi.fn(function () { return zipRef.current; }), }, })); vi.mock("formdata-node/file-from-path", () => ({ fileFromPath: vi.fn().mockReturnValue(new Blob([new ArrayBuffer(8)])), })); const config = { client: { rootDir: "rootDir", buildDir: "buildDir", archiveFile: "embeddable-build.zip", customCanvasCss: "src/custom-canvas.css", }, pushBaseUrl: "http://localhost:3000", previewBaseUrl: "http://localhost:3000", pushComponents: true, }; describe("push", () => { const zipMock = { addFile: vi.fn(), end: vi.fn(), outputStream: { pipe: vi.fn() }, }; beforeEach(() => { vi.mocked(checkNodeVersion).mockResolvedValue(true); vi.mocked(checkBuildSuccess).mockResolvedValue(true); vi.mocked(getArgumentByKey).mockReturnValue(undefined); vi.mocked(provideConfig).mockResolvedValue( config as ResolvedEmbeddableConfig ); vi.mocked(fs.access).mockResolvedValue(undefined); vi.mocked(fs.readFile).mockImplementation(async () => Buffer.from(`{"access_token":"mocked-token"}`) ); vi.mocked(fs.stat).mockResolvedValue({ size: 100, } as any); vi.mocked(findFiles).mockResolvedValue([["fileName", "filePath"]]); vi.mocked(fileFromPath).mockReturnValue( new Blob([new ArrayBuffer(8)]) as any ); zipRef.current = zipMock; vi.mocked(fsSync.readdirSync).mockReturnValue([]); vi.mocked(fsSync.existsSync).mockReturnValue(true); vi.mocked(fsSync.createWriteStream).mockReturnValue({ on: (event: string, cb: () => void) => { cb(); }, end: vi.fn(), } as any); vi.spyOn(process, "exit").mockImplementation(() => null as never); }); it("should push the build", async () => { await push(); expect(provideConfig).toHaveBeenCalled(); expect(checkNodeVersion).toHaveBeenCalled(); expect(checkBuildSuccess).toHaveBeenCalled(); expect(fs.access).toHaveBeenCalledWith(config.client.buildDir); expect(yazl.ZipFile).toHaveBeenCalled(); expect(fsSync.createWriteStream).toHaveBeenCalledWith( config.client.archiveFile ); expect(zipMock.outputStream.pipe).toHaveBeenCalled(); expect(zipMock.addFile).toHaveBeenCalledWith( "src/custom-canvas.css", "global.css", expect.objectContaining({ compress: true }) ); expect(fsSync.readdirSync).toHaveBeenCalledWith( "buildDir", expect.objectContaining({ recursive: true }) ); expect(zipMock.end).toHaveBeenCalled(); // after publishing the file gets removed expect(fs.rm).toHaveBeenCalledWith(config.client.archiveFile); expect(infoMock.info).toHaveBeenCalledWith( "Publishing to mocked-workspace-name using http://localhost:3000/workspace/mocked-workspace-id..." ); expect(infoMock.succeed).toHaveBeenCalledWith( "Published to mocked-workspace-name using http://localhost:3000/workspace/mocked-workspace-id" ); }); it("should fail if there are no workspaces", async () => { vi.spyOn(console, "error").mockImplementation(() => undefined); server.use( http.get("**/workspace", () => { return HttpResponse.json([]); }) ); await push(); expect(startMock.fail).toHaveBeenCalledWith("No workspaces found"); expect(process.exit).toHaveBeenCalledWith(1); expect(reportErrorToRollbar).toHaveBeenCalled(); }); it("should fail if the build is not successful", async () => { vi.spyOn(console, "error").mockImplementation(() => undefined); vi.mocked(checkBuildSuccess).mockResolvedValue(false); await push(); expect(process.exit).toHaveBeenCalledWith(1); expect(console.error).toHaveBeenCalledWith( "Build failed or not completed. Please run `embeddable:build` first." ); }); it("should push by api key provided in the arguments", async () => { vi.mocked(getArgumentByKey).mockReturnValue("mocked-api-key"); Object.defineProperties(process, { argv: { value: [ "--api-key", "mocked-api-key", "--email", "mocked-email@valid.com", ], }, }); await push(); expect(startMock.succeed).toHaveBeenCalledWith("Published using API key"); }); describe("push configuration", () => { it("should fail if pushModels, pushComponents, and pushEmbeddables are all disabled", async () => { vi.spyOn(console, "error").mockImplementation(() => undefined); vi.mocked(provideConfig).mockResolvedValue({ ...config, pushModels: false, pushComponents: false, pushEmbeddables: false, } as ResolvedEmbeddableConfig); await push(); expect(startMock.fail).toHaveBeenCalledWith( "Cannot push: pushModels, pushComponents, and pushEmbeddables are all disabled" ); expect(process.exit).toHaveBeenCalledWith(1); }); it("should only include component files when pushModels is false", async () => { const localZipMock = { addFile: vi.fn(), end: vi.fn(), outputStream: { pipe: vi.fn() }, }; zipRef.current = localZipMock; vi.mocked(provideConfig).mockResolvedValue({ ...config, pushModels: false, pushComponents: true, } as ResolvedEmbeddableConfig); await push(); // Should read component build directory expect(fsSync.readdirSync).toHaveBeenCalledWith( "buildDir", expect.objectContaining({ recursive: true }) ); // Should include global.css and client context files expect(localZipMock.addFile).toHaveBeenCalledWith( expect.anything(), "global.css", expect.objectContaining({ compress: true }) ); }); }); describe("API key validation", () => { it("should fail if API key is not provided", async () => { vi.spyOn(console, "error").mockImplementation(() => undefined); Object.defineProperties(process, { argv: { value: ["--api-key"], }, }); vi.mocked(getArgumentByKey).mockReturnValue(undefined); await push(); expect(startMock.fail).toHaveBeenCalledWith("No API key provided"); expect(process.exit).toHaveBeenCalledWith(1); }); it("should fail if email is not provided with API key", async () => { vi.spyOn(console, "error").mockImplementation(() => undefined); Object.defineProperties(process, { argv: { value: ["--api-key", "some-key"], }, }); vi.mocked(getArgumentByKey) .mockReturnValueOnce("some-key") // API key .mockReturnValueOnce(undefined); // Email await push(); expect(startMock.fail).toHaveBeenCalledWith( "Invalid email provided. Please provide a valid email using --email (-e) flag" ); expect(process.exit).toHaveBeenCalledWith(1); }); it("should fail if email is invalid", async () => { vi.spyOn(console, "error").mockImplementation(() => undefined); Object.defineProperties(process, { argv: { value: ["--api-key", "some-key", "--email", "invalid-email"], }, }); vi.mocked(getArgumentByKey) .mockReturnValueOnce("some-key") // API key .mockReturnValueOnce("invalid-email"); // Invalid email await push(); expect(startMock.fail).toHaveBeenCalledWith( "Invalid email provided. Please provide a valid email using --email (-e) flag" ); expect(process.exit).toHaveBeenCalledWith(1); }); it("should accept optional message parameter", async () => { Object.defineProperties(process, { argv: { value: [ "--api-key", "some-key", "--email", "valid@email.com", "--message", "test message", "--cube-version", "v1.34", ], }, }); vi.mocked(getArgumentByKey).mockImplementation((keysArg) => { const key = Array.isArray(keysArg) ? keysArg[0] : keysArg; if (key === "--api-key") return "some-key"; if (key === "--email") return "valid@email.com"; if (key === "--message") return "test message"; if (key === "--cube-version") return "v1.34"; return undefined; }); await push(); expect(startMock.succeed).toHaveBeenCalledWith("Published using API key"); }); }); describe("error handling", () => { beforeEach(() => { // Reset all mocks to their default state vi.mocked(getArgumentByKey).mockReturnValue(undefined); Object.defineProperties(process, { argv: { value: [], }, }); }); it("should fail if build directory does not exist", async () => { vi.spyOn(console, "error").mockImplementation(() => undefined); vi.mocked(fs.access).mockRejectedValue(new Error("No such directory")); vi.mocked(provideConfig).mockResolvedValue( config as ResolvedEmbeddableConfig ); await push(); expect(console.error).toHaveBeenCalledWith( "No embeddable build was produced." ); expect(process.exit).toHaveBeenCalledWith(1); }); it("should fail if token is not available", async () => { vi.spyOn(console, "error").mockImplementation(() => undefined); vi.mocked(fs.access).mockResolvedValue(undefined); vi.mocked(fs.readFile).mockResolvedValue(Buffer.from("{}")); vi.mocked(provideConfig).mockResolvedValue( config as ResolvedEmbeddableConfig ); await push(); expect(console.error).toHaveBeenCalledWith( 'Unauthorized. Please login using "npm run embeddable:login"' ); expect(process.exit).toHaveBeenCalledWith(1); }); it("should handle and report errors during push", async () => { const testError = new Error("Test error"); vi.mocked(provideConfig).mockRejectedValue(testError); vi.mocked(fs.access).mockResolvedValue(undefined); vi.mocked(fs.readFile).mockImplementation(async () => Buffer.from(`{"access_token":"mocked-token"}`) ); await push(); expect(reportErrorToRollbar).toHaveBeenCalledWith(testError); expect(process.exit).toHaveBeenCalledWith(1); }); }); describe("buildArchive", () => { type MockZip = { addFile: ReturnType<typeof vi.fn>; end: ReturnType<typeof vi.fn>; outputStream: { pipe: ReturnType<typeof vi.fn> }; }; let mockZipLocal: MockZip; beforeEach(() => { mockZipLocal = { addFile: vi.fn(), end: vi.fn(), outputStream: { pipe: vi.fn() }, }; zipRef.current = mockZipLocal; vi.mocked(fsSync.readdirSync).mockReturnValue([]); vi.mocked(fsSync.existsSync).mockReturnValue(true); vi.mocked(findFiles).mockResolvedValue([]); }); it("should include all file types when both flags are true", async () => { vi.mocked(findFiles) .mockResolvedValueOnce([ ["model1.cube.yml", "/path/to/model1.cube.yml"], ["model2.cube.yaml", "/path/to/model2.cube.yaml"], ]) .mockResolvedValueOnce([ ["context1.sc.yml", "/path/to/context1.sc.yml"], ["context2.cc.yml", "/path/to/context2.cc.yml"], ]); const testConfig = { ...config, pushModels: true, pushComponents: true, client: { ...config.client, srcDir: "/src", }, } as ResolvedEmbeddableConfig; await buildArchive(testConfig); // Should read component build directory expect(fsSync.readdirSync).toHaveBeenCalledWith( testConfig.client.buildDir, expect.objectContaining({ recursive: true }) ); // Should include global.css expect(mockZipLocal.addFile).toHaveBeenCalledWith( testConfig.client.customCanvasCss, "global.css", expect.objectContaining({ compress: true }) ); // Should include all model files expect(mockZipLocal.addFile).toHaveBeenCalledWith( "/path/to/model1.cube.yml", "model1.cube.yml", expect.objectContaining({ compress: true }) ); expect(mockZipLocal.addFile).toHaveBeenCalledWith( "/path/to/model2.cube.yaml", "model2.cube.yaml", expect.objectContaining({ compress: true }) ); // Should include all preset files expect(mockZipLocal.addFile).toHaveBeenCalledWith( "/path/to/context1.sc.yml", "context1.sc.yml", expect.objectContaining({ compress: true }) ); expect(mockZipLocal.addFile).toHaveBeenCalledWith( "/path/to/context2.cc.yml", "context2.cc.yml", expect.objectContaining({ compress: true }) ); }); it("should only include component files when pushModels is false", async () => { const testConfig = { ...config, pushModels: false, pushComponents: true, client: { ...config.client, srcDir: "/src", }, } as ResolvedEmbeddableConfig; await buildArchive(testConfig); // Should read component build directory expect(fsSync.readdirSync).toHaveBeenCalledWith( testConfig.client.buildDir, expect.objectContaining({ recursive: true }) ); // Should include global.css expect(mockZipLocal.addFile).toHaveBeenCalledWith( testConfig.client.customCanvasCss, "global.css", expect.objectContaining({ compress: true }) ); // Should only find client context files expect(findFiles).toHaveBeenCalledOnce(); }); it("should search in custom directories for model files", async () => { const testConfig = { ...config, pushModels: true, pushComponents: true, client: { ...config.client, srcDir: "/src", modelsSrc: "/custom/models/path", presetsSrc: "/custom/presets/path", }, } as ResolvedEmbeddableConfig; await buildArchive(testConfig); expect(findFiles).toHaveBeenCalledWith( "/custom/models/path", expect.any(RegExp) ); expect(findFiles).toHaveBeenCalledWith( "/custom/presets/path", expect.any(RegExp) ); }); it("should add directory files to zip with correct relative paths", async () => { vi.mocked(fsSync.readdirSync).mockReturnValue([ "index.js", "components/widget.js", ] as any); vi.mocked(fsSync.statSync).mockReturnValue({ isFile: () => true } as any); const testConfig = { ...config, pushModels: false, pushComponents: true, client: { ...config.client, srcDir: "/src", }, } as ResolvedEmbeddableConfig; await buildArchive(testConfig); expect(mockZipLocal.addFile).toHaveBeenCalledWith( path.join("buildDir", "index.js"), "index.js", expect.objectContaining({ compress: true }) ); expect(mockZipLocal.addFile).toHaveBeenCalledWith( path.join("buildDir", "components/widget.js"), "components/widget.js", expect.objectContaining({ compress: true }) ); }); it("should skip directories when adding directory contents to zip", async () => { vi.mocked(fsSync.readdirSync).mockReturnValue(["components"] as any); vi.mocked(fsSync.statSync).mockReturnValue({ isFile: () => false } as any); const testConfig = { ...config, pushModels: false, pushComponents: true, client: { ...config.client, srcDir: "/src", }, } as ResolvedEmbeddableConfig; await buildArchive(testConfig); const addFileCalls = mockZipLocal.addFile.mock.calls; const dirCall = addFileCalls.find( (call: any) => call[1] === "components" ); expect(dirCall).toBeUndefined(); }); it("should skip customCanvasCss when file does not exist", async () => { vi.mocked(fsSync.existsSync).mockReturnValue(false); const testConfig = { ...config, pushModels: false, pushComponents: true, client: { ...config.client, srcDir: "/src", }, } as ResolvedEmbeddableConfig; await buildArchive(testConfig); const addFileCalls = mockZipLocal.addFile.mock.calls; const cssCall = addFileCalls.find( (call: any) => call[1] === "global.css" ); expect(cssCall).toBeUndefined(); }); it("should skip buildDir when pushComponents is false (even if it exists)", async () => { vi.mocked(fsSync.existsSync).mockReturnValue(true); const testConfig = { ...config, pushModels: true, pushComponents: false, client: { ...config.client, srcDir: "/src", }, } as ResolvedEmbeddableConfig; await expect(buildArchive(testConfig)).resolves.not.toThrow(); expect(fsSync.readdirSync).not.toHaveBeenCalled(); }); it("should use srcDir as fallback when modelsSrc/presetsSrc are not defined", async () => { const testConfig = { ...config, pushModels: true, pushComponents: true, client: { ...config.client, srcDir: "/src", modelsSrc: undefined, presetsSrc: undefined, }, } as unknown as ResolvedEmbeddableConfig; await buildArchive(testConfig); expect(findFiles).toHaveBeenCalledWith("/src", expect.any(RegExp)); expect(findFiles).toHaveBeenCalledWith("/src", expect.any(RegExp)); }); it("should include embeddable files when pushEmbeddables is true", async () => { vi.mocked(findFiles) .mockResolvedValueOnce([]) // cube files .mockResolvedValueOnce([]) // security context files .mockResolvedValueOnce([]) // client context files .mockResolvedValueOnce([ ["dashboard.embeddable.yaml", "/src/dashboard.embeddable.yaml"], ["report.embeddable.yml", "/src/report.embeddable.yml"], ]); // embeddable files const testConfig = { ...config, pushModels: true, pushComponents: true, pushEmbeddables: true, client: { ...config.client, srcDir: "/src", }, } as ResolvedEmbeddableConfig; await buildArchive(testConfig); expect(findFiles).toHaveBeenCalledWith("/src", EMBEDDABLE_FILES); expect(mockZipLocal.addFile).toHaveBeenCalledWith( "/src/dashboard.embeddable.yaml", "dashboard.embeddable.yaml", expect.objectContaining({ compress: true }), ); expect(mockZipLocal.addFile).toHaveBeenCalledWith( "/src/report.embeddable.yml", "report.embeddable.yml", expect.objectContaining({ compress: true }), ); }); it("should not search for embeddable files when pushEmbeddables is false", async () => { // Reset call history to avoid interference from previous tests vi.mocked(findFiles).mockClear(); const testConfig = { ...config, pushModels: true, pushComponents: true, pushEmbeddables: false, client: { ...config.client, srcDir: "/src", }, } as ResolvedEmbeddableConfig; await buildArchive(testConfig); // findFiles should have been called for models and components but NOT for embeddable files const embeddableFilesCall = vi .mocked(findFiles) .mock.calls.find((call) => call[1] === EMBEDDABLE_FILES); expect(embeddableFilesCall).toBeUndefined(); }); it("should exit when all three push flags are disabled", async () => { vi.spyOn(process, "exit").mockImplementation(() => null as never); const testConfig = { ...config, pushModels: false, pushComponents: false, pushEmbeddables: false, client: { ...config.client, srcDir: "/src", }, } as ResolvedEmbeddableConfig; await buildArchive(testConfig); expect(process.exit).toHaveBeenCalledWith(1); }); }); describe("EMBEDDABLE_FILES", () => { it("should match .embeddable.yaml files", () => { expect(EMBEDDABLE_FILES.test("my-dashboard.embeddable.yaml")).toBe(true); expect(EMBEDDABLE_FILES.test("src/dashboards/sales.embeddable.yaml")).toBe( true, ); }); it("should match .embeddable.yml files", () => { expect(EMBEDDABLE_FILES.test("my-dashboard.embeddable.yml")).toBe(true); expect(EMBEDDABLE_FILES.test("src/widgets/chart.embeddable.yml")).toBe( true, ); }); it("should not match unrelated yaml files", () => { expect(EMBEDDABLE_FILES.test("config.yaml")).toBe(false); expect(EMBEDDABLE_FILES.test("my-dashboard.cc.yaml")).toBe(false); expect(EMBEDDABLE_FILES.test("my-dashboard.sc.yaml")).toBe(false); }); it("should not match cube files", () => { expect(EMBEDDABLE_FILES.test("my-model.cube.yaml")).toBe(false); expect(EMBEDDABLE_FILES.test("my-model.cube.yml")).toBe(false); }); it("should not match non-yaml extensions", () => { expect(EMBEDDABLE_FILES.test("my-dashboard.embeddable.json")).toBe(false); expect(EMBEDDABLE_FILES.test("my-dashboard.embeddable.ts")).toBe(false); }); }); describe.each([ { label: "sendBuild", endpoint: "**/bundle/:workspaceId/upload", invoke: (testConfig: ResolvedEmbeddableConfig, skipModelCheck?: boolean) => sendBuild(testConfig, { workspaceId: "test-workspace", token: "test-token", skipModelCheck }), }, { label: "sendBuildByApiKey", endpoint: "**/bundle/upload", invoke: (testConfig: ResolvedEmbeddableConfig, skipModelCheck?: boolean) => sendBuildByApiKey(testConfig, { apiKey: "test-api-key", email: "test@example.com", skipModelCheck }), }, ])("$label", ({ endpoint, invoke }) => { async function captureMetadata(pushEmbeddables: boolean, skipModelCheck?: boolean) { let capturedMetadata: Record<string, any> | undefined; server.use( http.post(endpoint, async ({ request }) => { const formData = await request.formData(); const requestBlob = formData.get("request") as Blob; capturedMetadata = JSON.parse(await requestBlob.text()); return HttpResponse.json({ bundleId: "mocked-bundle-id" }); }), ); const testConfig = { ...config, pushEmbeddables, region: "us", starterEmbeddables: {}, } as unknown as ResolvedEmbeddableConfig; await invoke(testConfig, skipModelCheck); return capturedMetadata; } it("should include pushEmbeddables in form data metadata when true", async () => { const metadata = await captureMetadata(true); expect(metadata?.pushEmbeddables).toBe(true); }); it("should include pushEmbeddables in form data metadata when false", async () => { const metadata = await captureMetadata(false); expect(metadata?.pushEmbeddables).toBe(false); }); it("should include skipModelCheck in form data metadata when true", async () => { const metadata = await captureMetadata(true, true); expect(metadata?.skipModelCheck).toBe(true); }); it("should not include skipModelCheck in form data metadata when false", async () => { const metadata = await captureMetadata(true, false); expect(metadata?.skipModelCheck).toBeUndefined(); }); }); describe("sendBuild warning suppression", () => { beforeEach(() => { warnMock.mockClear(); }); async function invokeSendBuildWithWarnings( flags: { pushModels?: boolean; pushComponents?: boolean; pushEmbeddables?: boolean }, warnings: string[], ) { server.use( http.post("**/bundle/:workspaceId/upload", () => HttpResponse.json({ bundleId: "mocked-bundle-id", warnings }), ), ); const testConfig = { ...config, pushModels: flags.pushModels ?? true, pushComponents: flags.pushComponents ?? true, pushEmbeddables: flags.pushEmbeddables ?? true, region: "us", starterEmbeddables: {}, } as unknown as ResolvedEmbeddableConfig; await sendBuild(testConfig, { workspaceId: "test-workspace", token: "test-token" }); } it("should suppress WARN-005 when pushEmbeddables is false", async () => { await invokeSendBuildWithWarnings({ pushEmbeddables: false }, ["WARN-005: Embeddable upload disabled"]); expect(warnMock).not.toHaveBeenCalled(); }); it("should suppress WARN-001 when pushModels is false", async () => { await invokeSendBuildWithWarnings({ pushModels: false }, ["WARN-001: Model upload disabled"]); expect(warnMock).not.toHaveBeenCalled(); }); it("should suppress WARN-003 when pushComponents is false", async () => { await invokeSendBuildWithWarnings({ pushComponents: false }, ["WARN-003: Component upload disabled"]); expect(warnMock).not.toHaveBeenCalled(); }); it("should still show unexpected warnings even when some flags are false", async () => { await invokeSendBuildWithWarnings( { pushEmbeddables: false }, ["WARN-005: Embeddable upload disabled", "WARN-999: Something unexpected"], ); expect(warnMock).toHaveBeenCalledWith("WARN-999: Something unexpected"); }); it("should not suppress warnings with a code that only starts with a suppressed code", async () => { await invokeSendBuildWithWarnings( { pushModels: false }, ["WARN-0010: Some other warning"], ); expect(warnMock).toHaveBeenCalledWith("WARN-0010: Some other warning"); }); it("should show all warnings when all flags are true", async () => { await invokeSendBuildWithWarnings( { pushModels: true, pushComponents: true, pushEmbeddables: true }, ["WARN-001: Model upload disabled"], ); expect(warnMock).toHaveBeenCalledWith("WARN-001: Model upload disabled"); }); }); });