UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

570 lines (472 loc) 18.4 kB
import { Alepha } from "alepha"; import { FileSystemProvider, MemoryFileSystemProvider, MemoryShellProvider, ShellProvider, } from "alepha/system"; import { describe, it } from "vitest"; import { VendorService } from "../services/VendorService.ts"; describe("VendorService", () => { const createTestEnv = () => { const alepha = Alepha.create() .with({ provide: ShellProvider, use: MemoryShellProvider }) .with({ provide: FileSystemProvider, use: MemoryFileSystemProvider }); const service = alepha.inject(VendorService); const shell = alepha.inject(MemoryShellProvider); const fs = alepha.inject(MemoryFileSystemProvider); return { service, shell, fs }; }; /** * Helper to create a test service with a stubbed clone that returns * a known directory, plus stubs getCommitHash to return a fixed hash. */ const createTestService = (shell: MemoryShellProvider) => { class TestVendorService extends VendorService { protected override async cloneRemote(): Promise<string> { await shell.run( "git clone --depth 1 --branch main --filter=blob:none remote /tmp/test-clone", ); return "/tmp/test-clone"; } protected override async cloneAtCommit(): Promise<string> { return "/tmp/test-baseline"; } protected override async getCommitHash(): Promise<string> { return "abc123"; } } const alepha = Alepha.create() .with({ provide: ShellProvider, use: MemoryShellProvider }) .with({ provide: FileSystemProvider, use: MemoryFileSystemProvider }); return { service: alepha.inject(TestVendorService), fs: alepha.inject(MemoryFileSystemProvider), }; }; describe("sync", () => { it("should clone the remote repository", async ({ expect }) => { const { service, shell, fs } = createTestEnv(); await fs.mkdir("/tmp/.alepha-vendor-fake/packages/my-pkg", { recursive: true, }); await fs.writeFile( "/tmp/.alepha-vendor-fake/packages/my-pkg/index.ts", "export {}", ); await service.sync({ root: "/project", remote: "git@github.com:user/repo.git", branch: "main", dir: "packages", packages: ["my-pkg"], }); expect( shell.wasCalledMatching( /git clone --depth 1 --branch main --filter=blob:none git@github.com:user\/repo\.git/, ), ).toBe(true); }); it("should remove local package dir before copying", async ({ expect }) => { const { service, fs } = createTestService( Alepha.create() .with({ provide: ShellProvider, use: MemoryShellProvider }) .inject(MemoryShellProvider), ); await fs.mkdir("/tmp/test-clone/packages/my-pkg", { recursive: true, }); await fs.writeFile( "/tmp/test-clone/packages/my-pkg/index.ts", "export {}", ); await fs.mkdir("/project/packages/my-pkg", { recursive: true }); await fs.writeFile("/project/packages/my-pkg/old-file.ts", "old"); const result = await service.sync({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: ["my-pkg"], force: true, }); expect(result.synced).toEqual(["my-pkg"]); expect(result.errors).toEqual([]); expect(fs.wasDeleted("/project/packages/my-pkg")).toBe(true); }); it("should report errors for missing remote packages", async ({ expect, }) => { const { service, fs } = createTestService( Alepha.create() .with({ provide: ShellProvider, use: MemoryShellProvider }) .inject(MemoryShellProvider), ); await fs.mkdir("/tmp/test-clone/packages", { recursive: true }); const result = await service.sync({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: ["missing-pkg"], }); expect(result.synced).toEqual([]); expect(result.errors).toEqual([ 'Package "missing-pkg" not found in remote', ]); }); it("should sync multiple packages", async ({ expect }) => { const { service, fs } = createTestService( Alepha.create() .with({ provide: ShellProvider, use: MemoryShellProvider }) .inject(MemoryShellProvider), ); await fs.mkdir("/tmp/test-clone/packages/pkg-a", { recursive: true }); await fs.writeFile("/tmp/test-clone/packages/pkg-a/index.ts", "a"); await fs.mkdir("/tmp/test-clone/packages/pkg-b", { recursive: true }); await fs.writeFile("/tmp/test-clone/packages/pkg-b/index.ts", "b"); const result = await service.sync({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: ["pkg-a", "pkg-b"], force: true, }); expect(result.synced).toEqual(["pkg-a", "pkg-b"]); expect(result.errors).toEqual([]); }); it("should clean up temp directory even on error", async ({ expect }) => { class TestVendorService extends VendorService { protected override async cloneRemote(): Promise<string> { return "/tmp/test-clone"; } protected override async getCommitHash(): Promise<string> { return "abc123"; } } const alepha = Alepha.create() .with({ provide: ShellProvider, use: MemoryShellProvider }) .with({ provide: FileSystemProvider, use: MemoryFileSystemProvider }); const testService = alepha.inject(TestVendorService); const testFs = alepha.inject(MemoryFileSystemProvider); await testFs.mkdir("/tmp/test-clone/packages/pkg-a", { recursive: true }); await testFs.writeFile("/tmp/test-clone/packages/pkg-a/index.ts", "a"); /** * Make cp throw to simulate a failure mid-sync. */ const originalCp = testFs.cp.bind(testFs); testFs.cp = async () => { throw new Error("disk full"); }; await expect( testService.sync({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: ["pkg-a"], force: true, }), ).rejects.toThrow("disk full"); expect(testFs.wasDeleted("/tmp/test-clone")).toBe(true); testFs.cp = originalCp; }); it("should write vendor.json after successful sync", async ({ expect }) => { const { service, fs } = createTestService( Alepha.create() .with({ provide: ShellProvider, use: MemoryShellProvider }) .inject(MemoryShellProvider), ); await fs.mkdir("/tmp/test-clone/packages/my-pkg", { recursive: true }); await fs.writeFile("/tmp/test-clone/packages/my-pkg/index.ts", "code"); await service.sync({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: ["my-pkg"], }); expect(fs.wasWritten("/project/packages/vendor.json")).toBe(true); const content = await fs.readFile("/project/packages/vendor.json"); const lock = JSON.parse(content.toString()); expect(lock.commit).toBe("abc123"); expect(lock.remote).toBe("remote"); }); it("should skip modification check on first sync (no vendor.json)", async ({ expect, }) => { const { service, fs } = createTestService( Alepha.create() .with({ provide: ShellProvider, use: MemoryShellProvider }) .inject(MemoryShellProvider), ); await fs.mkdir("/tmp/test-clone/packages/my-pkg", { recursive: true }); await fs.writeFile("/tmp/test-clone/packages/my-pkg/index.ts", "code"); const result = await service.sync({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: ["my-pkg"], }); expect(result.synced).toEqual(["my-pkg"]); expect(result.aborted).toBeUndefined(); }); it("should abort when local modifications detected against baseline", async ({ expect, }) => { const { service, fs } = createTestService( Alepha.create() .with({ provide: ShellProvider, use: MemoryShellProvider }) .inject(MemoryShellProvider), ); // Baseline (last synced state) await fs.mkdir("/tmp/test-baseline/packages/my-pkg", { recursive: true }); await fs.writeFile( "/tmp/test-baseline/packages/my-pkg/index.ts", "original", ); // Local (user modified) await fs.mkdir("/project/packages/my-pkg", { recursive: true }); await fs.writeFile("/project/packages/my-pkg/index.ts", "modified"); // Remote (latest) await fs.mkdir("/tmp/test-clone/packages/my-pkg", { recursive: true }); await fs.writeFile("/tmp/test-clone/packages/my-pkg/index.ts", "latest"); // Vendor lock exists from a previous sync await fs.mkdir("/project/packages", { recursive: true }); await fs.writeFile( "/project/packages/vendor.json", JSON.stringify({ remote: "remote", commit: "old-hash" }), ); const result = await service.sync({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: ["my-pkg"], }); expect(result.aborted).toBeDefined(); expect(result.synced).toEqual([]); }); it("should sync when local matches baseline", async ({ expect }) => { const { service, fs } = createTestService( Alepha.create() .with({ provide: ShellProvider, use: MemoryShellProvider }) .inject(MemoryShellProvider), ); // Baseline and local are identical await fs.mkdir("/tmp/test-baseline/packages/my-pkg", { recursive: true }); await fs.writeFile("/tmp/test-baseline/packages/my-pkg/index.ts", "same"); await fs.mkdir("/project/packages/my-pkg", { recursive: true }); await fs.writeFile("/project/packages/my-pkg/index.ts", "same"); // Remote has updates await fs.mkdir("/tmp/test-clone/packages/my-pkg", { recursive: true }); await fs.writeFile("/tmp/test-clone/packages/my-pkg/index.ts", "updated"); await fs.mkdir("/project/packages", { recursive: true }); await fs.writeFile( "/project/packages/vendor.json", JSON.stringify({ remote: "remote", commit: "old-hash" }), ); const result = await service.sync({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: ["my-pkg"], }); expect(result.synced).toEqual(["my-pkg"]); expect(result.aborted).toBeUndefined(); }); it("should skip modification check with --force even when vendor.json exists", async ({ expect, }) => { const { service, fs } = createTestService( Alepha.create() .with({ provide: ShellProvider, use: MemoryShellProvider }) .inject(MemoryShellProvider), ); // Local has modifications await fs.mkdir("/project/packages/my-pkg", { recursive: true }); await fs.writeFile("/project/packages/my-pkg/index.ts", "modified"); // Remote await fs.mkdir("/tmp/test-clone/packages/my-pkg", { recursive: true }); await fs.writeFile("/tmp/test-clone/packages/my-pkg/index.ts", "latest"); await fs.mkdir("/project/packages", { recursive: true }); await fs.writeFile( "/project/packages/vendor.json", JSON.stringify({ remote: "remote", commit: "old-hash" }), ); const result = await service.sync({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: ["my-pkg"], force: true, }); expect(result.synced).toEqual(["my-pkg"]); expect(result.aborted).toBeUndefined(); }); }); describe("diff", () => { const createDiffTestService = () => { class TestVendorService extends VendorService { protected override async cloneAtCommit(): Promise<string> { return "/tmp/test-baseline"; } } const alepha = Alepha.create() .with({ provide: ShellProvider, use: MemoryShellProvider }) .with({ provide: FileSystemProvider, use: MemoryFileSystemProvider }); return { service: alepha.inject(TestVendorService), fs: alepha.inject(MemoryFileSystemProvider), }; }; const writeVendorLock = async (fs: MemoryFileSystemProvider) => { await fs.mkdir("/project/packages", { recursive: true }); await fs.writeFile( "/project/packages/vendor.json", JSON.stringify({ remote: "remote", commit: "abc123" }), ); }; it("should return no changes when no vendor.json exists", async ({ expect, }) => { const { service } = createDiffTestService(); const result = await service.diff({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: ["pkg"], }); expect(result.packages).toEqual([]); expect(result.totalChanges).toBe(0); }); it("should detect locally added files", async ({ expect }) => { const { service, fs } = createDiffTestService(); await writeVendorLock(fs); // Baseline (last synced state) await fs.mkdir("/tmp/test-baseline/packages/pkg", { recursive: true }); await fs.writeFile("/tmp/test-baseline/packages/pkg/shared.ts", "same"); // Local (user added a file) await fs.mkdir("/project/packages/pkg", { recursive: true }); await fs.writeFile("/project/packages/pkg/shared.ts", "same"); await fs.writeFile("/project/packages/pkg/local-only.ts", "local"); const result = await service.diff({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: ["pkg"], }); expect(result.packages[0].added).toEqual(["local-only.ts"]); expect(result.totalChanges).toBe(1); }); it("should detect locally modified files", async ({ expect }) => { const { service, fs } = createDiffTestService(); await writeVendorLock(fs); await fs.mkdir("/tmp/test-baseline/packages/pkg", { recursive: true }); await fs.writeFile("/tmp/test-baseline/packages/pkg/file.ts", "original"); await fs.mkdir("/project/packages/pkg", { recursive: true }); await fs.writeFile("/project/packages/pkg/file.ts", "modified by user"); const result = await service.diff({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: ["pkg"], }); expect(result.packages[0].modified).toHaveLength(1); expect(result.packages[0].modified[0].file).toBe("file.ts"); expect(result.packages[0].modified[0].changes).toEqual([ { line: 1, type: "removed", text: "original" }, { line: 1, type: "added", text: "modified by user" }, ]); expect(result.totalChanges).toBe(1); }); it("should detect locally removed files", async ({ expect }) => { const { service, fs } = createDiffTestService(); await writeVendorLock(fs); // Baseline had two files await fs.mkdir("/tmp/test-baseline/packages/pkg", { recursive: true }); await fs.writeFile("/tmp/test-baseline/packages/pkg/kept.ts", "same"); await fs.writeFile("/tmp/test-baseline/packages/pkg/deleted.ts", "gone"); // Local only has one await fs.mkdir("/project/packages/pkg", { recursive: true }); await fs.writeFile("/project/packages/pkg/kept.ts", "same"); const result = await service.diff({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: ["pkg"], }); expect(result.packages[0].removed).toEqual(["deleted.ts"]); expect(result.totalChanges).toBe(1); }); it("should report no changes when local matches baseline", async ({ expect, }) => { const { service, fs } = createDiffTestService(); await writeVendorLock(fs); await fs.mkdir("/tmp/test-baseline/packages/pkg", { recursive: true }); await fs.writeFile("/tmp/test-baseline/packages/pkg/file.ts", "same"); await fs.mkdir("/project/packages/pkg", { recursive: true }); await fs.writeFile("/project/packages/pkg/file.ts", "same"); const result = await service.diff({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: ["pkg"], }); expect(result.totalChanges).toBe(0); }); it("should include line-level changes for multi-line modifications", async ({ expect, }) => { const { service, fs } = createDiffTestService(); await writeVendorLock(fs); await fs.mkdir("/tmp/test-baseline/packages/pkg", { recursive: true }); await fs.writeFile( "/tmp/test-baseline/packages/pkg/config.ts", "const a = 1;\nconst b = 2;\nconst c = 3;", ); await fs.mkdir("/project/packages/pkg", { recursive: true }); await fs.writeFile( "/project/packages/pkg/config.ts", "const a = 1;\nconst b = 99;\nconst c = 3;\nconst d = 4;", ); const result = await service.diff({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: ["pkg"], }); const fileDiff = result.packages[0].modified[0]; expect(fileDiff.file).toBe("config.ts"); expect(fileDiff.changes).toEqual([ { line: 2, type: "removed", text: "const b = 2;" }, { line: 2, type: "added", text: "const b = 99;" }, { line: 4, type: "added", text: "const d = 4;" }, ]); }); it("should clean up temp directory after diff", async ({ expect }) => { const { service, fs } = createDiffTestService(); await writeVendorLock(fs); await fs.mkdir("/tmp/test-baseline/packages", { recursive: true }); await service.diff({ root: "/project", remote: "remote", branch: "main", dir: "packages", packages: [], }); expect(fs.wasDeleted("/tmp/test-baseline")).toBe(true); }); }); });