Added gitea-mirror

This commit is contained in:
2026-01-19 08:34:20 +01:00
parent b956b07d1e
commit e19aff248d
385 changed files with 81357 additions and 0 deletions
@@ -0,0 +1,56 @@
/**
* Mock fetch utility for tests
*/
export function createMockResponse(data: any, options: {
ok?: boolean;
status?: number;
statusText?: string;
headers?: HeadersInit;
jsonError?: Error;
} = {}) {
const {
ok = true,
status = 200,
statusText = 'OK',
headers = { 'content-type': 'application/json' },
jsonError
} = options;
const response = {
ok,
status,
statusText,
headers: new Headers(headers),
json: async () => {
if (jsonError) {
throw jsonError;
}
return data;
},
text: async () => typeof data === 'string' ? data : JSON.stringify(data),
clone: function() {
// Return a new response object with the same properties
return createMockResponse(data, { ok, status, statusText, headers, jsonError });
}
};
return response;
}
export function mockFetch(handler: (url: string, options?: RequestInit) => any) {
return async (url: string, options?: RequestInit) => {
const result = await handler(url, options);
if (result && typeof result === 'object' && !result.clone) {
// If handler returns raw response properties, convert to mock response
if ('ok' in result || 'status' in result) {
const { ok, status, statusText, headers, json, text, ...data } = result;
const responseData = json ? await json() : (text ? await text() : data);
return createMockResponse(responseData, { ok, status, statusText, headers });
}
// If handler returns data directly, wrap it in a mock response
return createMockResponse(result);
}
return result;
};
}
+112
View File
@@ -0,0 +1,112 @@
/**
* Bun test setup file
* This file is automatically loaded before running tests
*/
import { mock } from "bun:test";
// Set NODE_ENV to test
process.env.NODE_ENV = "test";
// Mock setTimeout globally to prevent hanging tests
const originalSetTimeout = global.setTimeout;
global.setTimeout = ((fn: Function, delay?: number) => {
// In tests, execute immediately or with minimal delay
if (delay && delay > 100) {
// For long delays, execute immediately
Promise.resolve().then(() => fn());
} else {
// For short delays, use setImmediate-like behavior
Promise.resolve().then(() => fn());
}
return 0;
}) as any;
// Restore setTimeout for any code that needs real timing
(global as any).__originalSetTimeout = originalSetTimeout;
// Mock the database module to prevent real database access during tests
mock.module("@/lib/db", () => {
const mockDb = {
select: () => ({
from: () => ({
where: () => ({
limit: () => Promise.resolve([])
})
})
}),
insert: (table: any) => ({
values: (data: any) => Promise.resolve({ insertedId: "mock-id" })
}),
update: () => ({
set: () => ({
where: () => Promise.resolve()
})
}),
delete: () => ({
where: () => Promise.resolve()
})
};
return {
db: mockDb,
users: {},
events: {},
configs: {},
repositories: {},
mirrorJobs: {},
organizations: {},
sessions: {},
accounts: {},
verificationTokens: {},
oauthApplications: {},
oauthAccessTokens: {},
oauthConsent: {},
ssoProviders: {}
};
});
// Mock drizzle-orm to prevent database migrations from running
mock.module("drizzle-orm/bun-sqlite/migrator", () => {
return {
migrate: () => {}
};
});
// Mock config encryption utilities
mock.module("@/lib/utils/config-encryption", () => {
return {
decryptConfigTokens: (config: any) => {
// Return the config as-is for tests
return config;
},
encryptConfigTokens: (config: any) => {
// Return the config as-is for tests
return config;
},
getDecryptedGitHubToken: (config: any) => {
// Return the token as-is for tests
return config.githubConfig?.token || "";
},
getDecryptedGiteaToken: (config: any) => {
// Return the token as-is for tests
return config.giteaConfig?.token || "";
}
};
});
// Mock the helpers module to prevent database operations
mock.module("@/lib/helpers", () => {
const mockCreateMirrorJob = mock(() => Promise.resolve("mock-job-id"));
const mockCreateEvent = mock(() => Promise.resolve());
return {
createMirrorJob: mockCreateMirrorJob,
createEvent: mockCreateEvent,
// Add other helpers as needed
};
});
// Add DOM testing support if needed
// import { DOMParser } from "linkedom";
// global.DOMParser = DOMParser;
@@ -0,0 +1,161 @@
#!/usr/bin/env bun
/**
* Test script to validate Gitea authentication and permissions
* Run with: bun run src/tests/test-gitea-auth.ts
*/
import { validateGiteaAuth, canCreateOrganizations, validateGiteaConfigForMirroring } from "@/lib/gitea-auth-validator";
import { getConfigsByUserId } from "@/lib/db/queries/configs";
import { db, users } from "@/lib/db";
import { eq } from "drizzle-orm";
async function testGiteaAuthentication() {
console.log("=".repeat(60));
console.log("GITEA AUTHENTICATION TEST");
console.log("=".repeat(60));
try {
// Get the first user for testing
const userList = await db.select().from(users).limit(1);
if (userList.length === 0) {
console.error("❌ No users found in database. Please set up a user first.");
process.exit(1);
}
const user = userList[0];
console.log(`\n✅ Found user: ${user.email} (ID: ${user.id})`);
// Get the user's configuration
const configs = await getConfigsByUserId(user.id);
if (configs.length === 0) {
console.error("❌ No configuration found for user. Please configure Gitea settings.");
process.exit(1);
}
const config = configs[0];
console.log(`✅ Found configuration (ID: ${config.id})`);
if (!config.giteaConfig?.url || !config.giteaConfig?.token) {
console.error("❌ Gitea configuration is incomplete. URL or token is missing.");
process.exit(1);
}
console.log(`\n📡 Testing connection to: ${config.giteaConfig.url}`);
console.log("-".repeat(60));
// Test 1: Validate authentication
console.log("\n🔐 Test 1: Validating authentication...");
try {
const giteaUser = await validateGiteaAuth(config);
console.log(`✅ Authentication successful!`);
console.log(` - Username: ${giteaUser.username || giteaUser.login}`);
console.log(` - User ID: ${giteaUser.id}`);
console.log(` - Is Admin: ${giteaUser.is_admin}`);
console.log(` - Email: ${giteaUser.email || 'Not provided'}`);
} catch (error) {
console.error(`❌ Authentication failed: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
// Test 2: Check organization creation permissions
console.log("\n🏢 Test 2: Checking organization creation permissions...");
try {
const canCreate = await canCreateOrganizations(config);
if (canCreate) {
console.log(`✅ User can create organizations`);
} else {
console.log(`⚠️ User cannot create organizations (will use fallback to user account)`);
}
} catch (error) {
console.error(`❌ Error checking permissions: ${error instanceof Error ? error.message : String(error)}`);
}
// Test 3: Full validation for mirroring
console.log("\n🔍 Test 3: Full validation for mirroring...");
try {
const validation = await validateGiteaConfigForMirroring(config);
if (validation.valid) {
console.log(`✅ Configuration is valid for mirroring`);
} else {
console.log(`❌ Configuration is not valid for mirroring`);
}
if (validation.warnings.length > 0) {
console.log(`\n⚠️ Warnings:`);
validation.warnings.forEach(warning => {
console.log(` - ${warning}`);
});
}
if (validation.errors.length > 0) {
console.log(`\n❌ Errors:`);
validation.errors.forEach(error => {
console.log(` - ${error}`);
});
}
} catch (error) {
console.error(`❌ Validation error: ${error instanceof Error ? error.message : String(error)}`);
}
// Test 4: Check specific API endpoints
console.log("\n🔧 Test 4: Testing specific API endpoints...");
// Import HTTP client for direct API testing
const { httpGet } = await import("@/lib/http-client");
const { decryptConfigTokens } = await import("@/lib/utils/config-encryption");
const decryptedConfig = decryptConfigTokens(config);
// Test organization listing
try {
const orgsResponse = await httpGet(
`${config.giteaConfig.url}/api/v1/user/orgs`,
{
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
}
);
console.log(`✅ Can list organizations (found ${orgsResponse.data.length})`);
} catch (error) {
console.log(`⚠️ Cannot list organizations: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
// Test repository listing
try {
const reposResponse = await httpGet(
`${config.giteaConfig.url}/api/v1/user/repos`,
{
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
}
);
console.log(`✅ Can list repositories (found ${reposResponse.data.length})`);
} catch (error) {
console.error(`❌ Cannot list repositories: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
console.log("\n" + "=".repeat(60));
console.log("TEST COMPLETE");
console.log("=".repeat(60));
// Summary
console.log("\n📊 Summary:");
console.log(` - Gitea URL: ${config.giteaConfig.url}`);
console.log(` - Default Owner: ${config.giteaConfig.defaultOwner || 'Not set'}`);
console.log(` - Mirror Strategy: ${config.githubConfig?.mirrorStrategy || 'Not set'}`);
console.log(` - Organization: ${config.giteaConfig.organization || 'Not set'}`);
console.log(` - Preserve Org Structure: ${config.giteaConfig.preserveOrgStructure || false}`);
console.log("\n✨ All tests completed successfully!");
} catch (error) {
console.error("\n❌ Test failed with error:", error);
process.exit(1);
}
process.exit(0);
}
// Run the test
testGiteaAuthentication().catch(console.error);
@@ -0,0 +1,173 @@
#!/usr/bin/env bun
/**
* Test script to verify metadata mirroring authentication works correctly
* This tests the fix for issue #68 - "user does not exist [uid: 0, name: ]"
* Run with: bun run src/tests/test-metadata-mirroring.ts
*/
import { mirrorGitRepoIssuesToGitea } from "@/lib/gitea";
import { validateGiteaAuth } from "@/lib/gitea-auth-validator";
import { getConfigsByUserId } from "@/lib/db/queries/configs";
import { db, users, repositories } from "@/lib/db";
import { eq } from "drizzle-orm";
import { Octokit } from "@octokit/rest";
import type { Repository } from "@/lib/db/schema";
async function testMetadataMirroringAuth() {
console.log("=".repeat(60));
console.log("METADATA MIRRORING AUTHENTICATION TEST");
console.log("=".repeat(60));
try {
// Get the first user for testing
const userList = await db.select().from(users).limit(1);
if (userList.length === 0) {
console.error("❌ No users found in database. Please set up a user first.");
process.exit(1);
}
const user = userList[0];
console.log(`\n✅ Found user: ${user.email} (ID: ${user.id})`);
// Get the user's configuration
const configs = await getConfigsByUserId(user.id);
if (configs.length === 0) {
console.error("❌ No configuration found for user. Please configure GitHub and Gitea settings.");
process.exit(1);
}
const config = configs[0];
console.log(`✅ Found configuration (ID: ${config.id})`);
if (!config.giteaConfig?.url || !config.giteaConfig?.token) {
console.error("❌ Gitea configuration is incomplete. URL or token is missing.");
process.exit(1);
}
if (!config.githubConfig?.token) {
console.error("❌ GitHub configuration is incomplete. Token is missing.");
process.exit(1);
}
console.log(`\n📡 Testing Gitea connection to: ${config.giteaConfig.url}`);
console.log("-".repeat(60));
// Test 1: Validate Gitea authentication
console.log("\n🔐 Test 1: Validating Gitea authentication...");
let giteaUser;
try {
giteaUser = await validateGiteaAuth(config);
console.log(`✅ Gitea authentication successful!`);
console.log(` - Username: ${giteaUser.username || giteaUser.login}`);
console.log(` - User ID: ${giteaUser.id}`);
console.log(` - Is Admin: ${giteaUser.is_admin}`);
} catch (error) {
console.error(`❌ Gitea authentication failed: ${error instanceof Error ? error.message : String(error)}`);
console.error(` This is the root cause of the "user does not exist [uid: 0]" error`);
process.exit(1);
}
// Test 2: Check if we can access a test repository
console.log("\n📦 Test 2: Looking for a test repository...");
// Get a repository from the database
const repos = await db.select().from(repositories)
.where(eq(repositories.userId, user.id))
.limit(1);
if (repos.length === 0) {
console.log("⚠️ No repositories found in database. Skipping metadata mirroring test.");
console.log(" Please run a mirror operation first to test metadata mirroring.");
} else {
const testRepo = repos[0] as Repository;
console.log(`✅ Found test repository: ${testRepo.fullName}`);
// Test 3: Verify repository exists in Gitea
console.log("\n🔍 Test 3: Verifying repository exists in Gitea...");
const { isRepoPresentInGitea } = await import("@/lib/gitea");
const giteaOwner = giteaUser.username || giteaUser.login;
const repoExists = await isRepoPresentInGitea({
config,
owner: giteaOwner,
repoName: testRepo.name,
});
if (!repoExists) {
console.log(`⚠️ Repository ${testRepo.name} not found in Gitea at ${giteaOwner}`);
console.log(` This would cause metadata mirroring to fail with authentication errors`);
console.log(` Please ensure the repository is mirrored first before attempting metadata sync`);
} else {
console.log(`✅ Repository exists in Gitea at ${giteaOwner}/${testRepo.name}`);
// Test 4: Attempt to mirror metadata (dry run)
console.log("\n🔄 Test 4: Testing metadata mirroring authentication...");
try {
// Create Octokit instance
const octokit = new Octokit({
auth: config.githubConfig.token,
});
// Test by attempting to fetch labels (lightweight operation)
const { httpGet } = await import("@/lib/http-client");
const { decryptConfigTokens } = await import("@/lib/utils/config-encryption");
const decryptedConfig = decryptConfigTokens(config);
const labelsResponse = await httpGet(
`${config.giteaConfig.url}/api/v1/repos/${giteaOwner}/${testRepo.name}/labels`,
{
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
}
);
console.log(`✅ Successfully authenticated for metadata operations`);
console.log(` - Can access repository labels endpoint`);
console.log(` - Found ${labelsResponse.data.length} existing labels`);
console.log(` - Authentication token is valid and has proper permissions`);
} catch (error) {
if (error instanceof Error && error.message.includes('uid: 0')) {
console.error(`❌ CRITICAL: Authentication failed with "uid: 0" error!`);
console.error(` This is the exact issue from GitHub issue #68`);
console.error(` Error: ${error.message}`);
} else {
console.error(`❌ Metadata operation failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
}
console.log("\n" + "=".repeat(60));
console.log("TEST COMPLETE");
console.log("=".repeat(60));
// Summary
console.log("\n📊 Summary:");
console.log(` - Gitea URL: ${config.giteaConfig.url}`);
console.log(` - Gitea User: ${giteaUser?.username || giteaUser?.login || 'Unknown'}`);
console.log(` - Authentication: ${giteaUser ? '✅ Valid' : '❌ Invalid'}`);
console.log(` - Metadata Mirroring: ${config.giteaConfig.mirrorMetadata ? 'Enabled' : 'Disabled'}`);
if (config.giteaConfig.mirrorMetadata) {
console.log(` - Issues: ${config.giteaConfig.mirrorIssues ? 'Yes' : 'No'}`);
console.log(` - Pull Requests: ${config.giteaConfig.mirrorPullRequests ? 'Yes' : 'No'}`);
console.log(` - Labels: ${config.giteaConfig.mirrorLabels ? 'Yes' : 'No'}`);
console.log(` - Milestones: ${config.giteaConfig.mirrorMilestones ? 'Yes' : 'No'}`);
}
console.log("\n✨ If all tests passed, metadata mirroring should work without uid:0 errors!");
} catch (error) {
console.error("\n❌ Test failed with error:", error);
process.exit(1);
}
process.exit(0);
}
// Run the test
testMetadataMirroringAuth().catch(console.error);