import { describe, test, expect, mock, beforeEach, afterEach } from "bun:test"; import type { Config, Repository } from "./db/schema"; import { repoStatusEnum } from "@/types/Repository"; describe("Mirror Sync Fix Implementation", () => { let originalFetch: typeof global.fetch; beforeEach(() => { originalFetch = global.fetch; }); afterEach(() => { global.fetch = originalFetch; }); describe("Non-mirror repository recovery", () => { test("should detect and handle non-mirror repositories", async () => { const mockHandleNonMirrorRepo = async ({ config, repository, owner, }: { config: Partial; repository: Repository; owner: string; }) => { try { // First, check if the repo exists const checkResponse = await fetch( `${config.giteaConfig!.url}/api/v1/repos/${owner}/${repository.name}`, { headers: { Authorization: `token ${config.giteaConfig!.token}`, }, } ); if (!checkResponse.ok) { // Repo doesn't exist, we can create it as mirror return { action: "create_mirror", success: true }; } const repoInfo = await checkResponse.json(); if (!repoInfo.mirror) { // Repository exists but is not a mirror console.log(`Repository ${repository.name} exists but is not a mirror`); // Option 1: Delete and recreate if (config.giteaConfig?.autoFixNonMirrors) { const deleteResponse = await fetch( `${config.giteaConfig.url}/api/v1/repos/${owner}/${repository.name}`, { method: "DELETE", headers: { Authorization: `token ${config.giteaConfig.token}`, }, } ); if (deleteResponse.ok) { return { action: "deleted_for_recreation", success: true }; } } // Option 2: Mark for manual intervention return { action: "manual_intervention_required", success: false, reason: "Repository exists but is not configured as mirror", suggestion: `Delete ${owner}/${repository.name} in Gitea and re-run mirror`, }; } // Repository is already a mirror, can proceed with sync return { action: "sync_mirror", success: true }; } catch (error) { return { action: "error", success: false, error: error instanceof Error ? error.message : String(error), }; } }; // Test scenario 1: Non-mirror repository global.fetch = mock(async (url: string) => { if (url.includes("/api/v1/repos/starred/test-repo")) { return { ok: true, status: 200, headers: new Headers({ "content-type": "application/json" }), json: async () => ({ id: 123, name: "test-repo", mirror: false, // Not a mirror owner: { login: "starred" }, }), } as Response; } return originalFetch(url); }); const config: Partial = { giteaConfig: { url: "https://gitea.ui.com", token: "gitea-token", autoFixNonMirrors: false, // Manual intervention mode }, }; const repository: Repository = { id: "repo-123", name: "test-repo", isStarred: true, // ... other fields } as Repository; const result = await mockHandleNonMirrorRepo({ config, repository, owner: "starred", }); expect(result.action).toBe("manual_intervention_required"); expect(result.success).toBe(false); expect(result.suggestion).toContain("Delete starred/test-repo"); }); test("should successfully delete and prepare for recreation when autoFix is enabled", async () => { let deleteRequested = false; global.fetch = mock(async (url: string, options?: RequestInit) => { if (url.includes("/api/v1/repos/starred/test-repo")) { if (options?.method === "DELETE") { deleteRequested = true; return { ok: true, status: 204, } as Response; } // GET request return { ok: true, status: 200, headers: new Headers({ "content-type": "application/json" }), json: async () => ({ id: 123, name: "test-repo", mirror: false, owner: { login: "starred" }, }), } as Response; } return originalFetch(url, options); }); const config: Partial = { giteaConfig: { url: "https://gitea.ui.com", token: "gitea-token", autoFixNonMirrors: true, // Auto-fix enabled }, }; // Simulate the fix process const checkResponse = await fetch( `${config.giteaConfig!.url}/api/v1/repos/starred/test-repo`, { headers: { Authorization: `token ${config.giteaConfig!.token}`, }, } ); const repoInfo = await checkResponse.json(); expect(repoInfo.mirror).toBe(false); // Delete the non-mirror repo const deleteResponse = await fetch( `${config.giteaConfig!.url}/api/v1/repos/starred/test-repo`, { method: "DELETE", headers: { Authorization: `token ${config.giteaConfig!.token}`, }, } ); expect(deleteResponse.ok).toBe(true); expect(deleteRequested).toBe(true); }); }); describe("Enhanced mirror creation with validation", () => { test("should validate repository before creating mirror", async () => { const createMirrorWithValidation = async ({ config, repository, owner, }: { config: Partial; repository: Repository; owner: string; }) => { // Step 1: Check if repo already exists const checkResponse = await fetch( `${config.giteaConfig!.url}/api/v1/repos/${owner}/${repository.name}`, { headers: { Authorization: `token ${config.giteaConfig!.token}`, }, } ); if (checkResponse.ok) { const existingRepo = await checkResponse.json(); if (existingRepo.mirror) { return { created: false, reason: "already_mirror", repoId: existingRepo.id, }; } else { return { created: false, reason: "exists_not_mirror", repoId: existingRepo.id, }; } } // Step 2: Create as mirror const cloneUrl = repository.isPrivate ? repository.cloneUrl.replace("https://", `https://GITHUB_TOKEN@`) : repository.cloneUrl; const createResponse = await fetch( `${config.giteaConfig!.url}/api/v1/repos/migrate`, { method: "POST", headers: { Authorization: `token ${config.giteaConfig!.token}`, "Content-Type": "application/json", }, body: JSON.stringify({ clone_addr: cloneUrl, repo_name: repository.name, mirror: true, // Ensure this is always true repo_owner: owner, private: repository.isPrivate, description: `Mirrored from ${repository.fullName}`, service: "git", }), } ); if (createResponse.ok) { const newRepo = await createResponse.json(); return { created: true, reason: "success", repoId: newRepo.id, }; } const error = await createResponse.json(); return { created: false, reason: "create_failed", error: error.message, }; }; // Mock successful mirror creation global.fetch = mock(async (url: string, options?: RequestInit) => { if (url.includes("/api/v1/repos/starred/new-repo") && !options?.method) { return { ok: false, status: 404, } as Response; } if (url.includes("/api/v1/repos/migrate")) { const body = JSON.parse(options?.body as string); expect(body.mirror).toBe(true); // Validate mirror flag expect(body.repo_owner).toBe("starred"); return { ok: true, status: 201, headers: new Headers({ "content-type": "application/json" }), json: async () => ({ id: 456, name: body.repo_name, mirror: true, owner: { login: body.repo_owner }, }), } as Response; } return originalFetch(url, options); }); const config: Partial = { giteaConfig: { url: "https://gitea.ui.com", token: "gitea-token", }, }; const repository: Repository = { id: "repo-456", name: "new-repo", fullName: "original/new-repo", cloneUrl: "https://github.com/original/new-repo.git", isPrivate: false, isStarred: true, // ... other fields } as Repository; const result = await createMirrorWithValidation({ config, repository, owner: "starred", }); expect(result.created).toBe(true); expect(result.reason).toBe("success"); expect(result.repoId).toBe(456); }); }); describe("Sync status tracking", () => { test("should track sync attempts and failures", async () => { interface SyncAttempt { repositoryId: string; attemptNumber: number; timestamp: Date; error?: string; success: boolean; } const syncAttempts: Map = new Map(); const trackSyncAttempt = ( repositoryId: string, success: boolean, error?: string ) => { const attempts = syncAttempts.get(repositoryId) || []; attempts.push({ repositoryId, attemptNumber: attempts.length + 1, timestamp: new Date(), error, success, }); syncAttempts.set(repositoryId, attempts); }; const shouldRetrySync = (repositoryId: string): boolean => { const attempts = syncAttempts.get(repositoryId) || []; if (attempts.length === 0) return true; const lastAttempt = attempts[attempts.length - 1]; const timeSinceLastAttempt = Date.now() - lastAttempt.timestamp.getTime(); // Retry if: // 1. Less than 3 attempts // 2. At least 5 minutes since last attempt // 3. Last error was not "Repository is not a mirror" return ( attempts.length < 3 && timeSinceLastAttempt > 5 * 60 * 1000 && !lastAttempt.error?.includes("Repository is not a mirror") ); }; // Simulate sync attempts trackSyncAttempt("repo-123", false, "Repository is not a mirror"); trackSyncAttempt("repo-456", false, "Network timeout"); trackSyncAttempt("repo-456", true); expect(shouldRetrySync("repo-123")).toBe(false); // Non-retryable error expect(shouldRetrySync("repo-456")).toBe(false); // Already succeeded expect(shouldRetrySync("repo-789")).toBe(true); // No attempts yet }); }); });