Added gitea-mirror
This commit is contained in:
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user