From 206e241174a3a2f8474f4a0e523da2cfe1a4359d Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 30 Oct 2026 11:39:16 -0500 Subject: [PATCH] test(cloud): verify startup order and package dependency comparison Co-Authored-By: Paperclip --- ...d-configuration-migration-postgres.test.ts | 20 +++++++++++++++++++ .../server-startup-feedback-export.test.ts | 12 +++++++---- .../cloud-configuration-qualification.ts | 4 ++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/server/src/__tests__/cloud-configuration-migration-postgres.test.ts b/server/src/__tests__/cloud-configuration-migration-postgres.test.ts index 43582478155..dd747d06ac6 100644 --- a/server/src/__tests__/cloud-configuration-migration-postgres.test.ts +++ b/server/src/__tests__/cloud-configuration-migration-postgres.test.ts @@ -302,4 +302,24 @@ const keys = ["adapter-settings:v1", "cloud-runtime-config:v1", "cloud-configura expect(missing?.blockers).toContainEqual(expect.objectContaining({ code: "current_image_catalog_unavailable" })); expect((await migration().inspectStored())?.lastPackageMismatch?.blockers).toContainEqual(expect.objectContaining({ code: "current_image_catalog_unavailable" })); }); + + it.skipIf(process.platform === "win32")("detects changed pnpm dependency bytes in a builtin package overlap comparison", async () => { + await legacy(); + const image = fixture(); + await install(image); + await migration().migrate(); + const dependency = path.join(image.packageRoot, "node_modules", ".pnpm", "dep@1.0.0", "node_modules", "dep"); + await fs.mkdir(dependency, { recursive: true }); + await fs.writeFile(path.join(dependency, "index.js"), "export const value = 1;"); + await fs.symlink(path.relative(path.join(image.packageRoot, "node_modules"), dependency), path.join(image.packageRoot, "node_modules", "dep")); + const installs = image.installs.map(({ distribution: _distribution, ...install }) => install); + const loader = loaderFor(image); + const original = await migration().qualify({ installs, loadManifest: (root) => loader.loadManifest(root) }); + expect(original.configurationPackagesEligible).toBe(true); + await fs.writeFile(path.join(dependency, "index.js"), "export const value = 2;"); + const changed = await migration(peer, homeB).qualify({ installs, loadManifest: (root) => loader.loadManifest(root), expectedPackageEvidenceDigest: original.packageEvidenceDigest }); + expect(changed.configurationPackagesEligible).toBe(true); + expect(changed.samePackageOverlapEligible).toBe(false); + expect(changed.overlapBlockers).toContainEqual(expect.objectContaining({ code: "package_evidence_changed" })); + }); }); diff --git a/server/src/__tests__/server-startup-feedback-export.test.ts b/server/src/__tests__/server-startup-feedback-export.test.ts index 53360904b82..6942d87e8d3 100644 --- a/server/src/__tests__/server-startup-feedback-export.test.ts +++ b/server/src/__tests__/server-startup-feedback-export.test.ts @@ -641,6 +641,7 @@ describe("startServer feedback export wiring", () => { expect(cloudRuntimeStartupMocks.telemetry).toHaveBeenCalledExactlyOnceWith({ enabled: false }); expect(createBetterAuthInstanceMock).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ authDisableSignUp: true, port: 4321 }), expect.anything()); expect(started.listenPort).toBe(4321); + expect(cloudMigrationMocks.qualify.mock.invocationCallOrder[0]).toBeLessThan(createAppMock.mock.invocationCallOrder[0]!); } finally { release?.(); vi.unstubAllEnvs(); @@ -661,11 +662,14 @@ describe("startServer feedback export wiring", () => { }); it("stops before configuration consumers when the atomic Cloud migration fails", async () => { + vi.stubEnv("PAPERCLIP_CLOUD_TENANT_SERVER_TOKEN", "startup-test-token"); cloudMigrationMocks.migrate.mockRejectedValueOnce(new Error("migration did not commit")); - await expect(startServer()).rejects.toThrow("migration did not commit"); - expect(cloudRuntimeStartupMocks.initialize).not.toHaveBeenCalled(); - expect(cloudRuntimeStartupMocks.telemetry).not.toHaveBeenCalled(); - expect(createAppMock).not.toHaveBeenCalled(); + try { + await expect(startServer()).rejects.toThrow("migration did not commit"); + expect(cloudRuntimeStartupMocks.initialize).not.toHaveBeenCalled(); + expect(cloudRuntimeStartupMocks.telemetry).not.toHaveBeenCalled(); + expect(createAppMock).not.toHaveBeenCalled(); + } finally { vi.unstubAllEnvs(); } }); it.each([false, true])("registers runner evidence independently from the backup scan (%s)", async enabled => { diff --git a/server/src/services/cloud-configuration-qualification.ts b/server/src/services/cloud-configuration-qualification.ts index b069b253340..07094f0fc36 100644 --- a/server/src/services/cloud-configuration-qualification.ts +++ b/server/src/services/cloud-configuration-qualification.ts @@ -1,5 +1,5 @@ import type { Db } from "@paperclipai/db"; -import { BUNDLED_PLUGIN_CATALOG, resolveBundledCatalogRoot, resolveBundledPluginInstalls, type ResolvedBundledPlugin } from "./bundled-plugins.js"; +import { BUNDLED_PLUGIN_CATALOG, resolveBundledCatalogRoot, resolveBundledPluginInstalls, resolveManagedBundledPluginKeys, type ResolvedBundledPlugin } from "./bundled-plugins.js"; import { readDistributionPluginCatalog } from "./distribution-plugin-catalog.js"; import { createBundledPluginPackageResolver } from "./bundled-plugin-package.js"; import { cloudConfigurationMigrationService } from "./cloud-configuration-migration.js"; @@ -18,7 +18,7 @@ export async function qualifyCloudConfigurationForCurrentImage(db: Db, options: let installs: ResolvedBundledPlugin[]; try { const distribution = readDistributionPluginCatalog(catalogRoot, BUNDLED_PLUGIN_CATALOG); - installs = resolveBundledPluginInstalls(getManagedInstanceConfig(env)?.plugins.autoInstall ?? [], { + installs = resolveBundledPluginInstalls(resolveManagedBundledPluginKeys(getManagedInstanceConfig(env)?.plugins.autoInstall, env) ?? [], { catalogRoot, env, enforceCatalogRoot: true, distributionPlugins: distribution, }); } catch {