feat(web,ui): per-provider git forge API base URL and detection URLs
Configure a default API base URL per git provider (github/gitlab/gitea) in settings.json gitProviders, with GitHub Enterprise support (Octokit baseUrl + device-flow web origin derived from the API base), and replace the client-side custom-domains list with server-persisted detection URL chips (SSH/HTTPS forms normalized to hosts). The configured API base host auto-counts as a detection host. Settings round-trip through the existing /api/config/settings sanitizer; the UI store hydrates from server settings with a one-time localStorage migration.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { afterAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import { afterAll, afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
|
||||
@@ -201,7 +201,6 @@ describe('Gitea auth routes', () => {
|
||||
test('me returns the connected user', async () => {
|
||||
setGiteaAuth({ accessToken: 'gitea-a', baseUrl: 'gitea.example.com', user: aliceUser });
|
||||
scriptedFetch([(url) => (matches(/\/api\/v1\/user$/)(url) ? jsonResponse(aliceUser) : null)]);
|
||||
|
||||
const app = createApp();
|
||||
const response = await request(app).get('/api/gitea/me');
|
||||
expect(response.status).toBe(200);
|
||||
@@ -216,6 +215,47 @@ describe('Gitea auth routes', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Gitea configured default base URL', () => {
|
||||
const SETTINGS_FILE = path.join(TEMP_DATA_DIR, 'settings.json');
|
||||
|
||||
afterEach(() => {
|
||||
if (fs.existsSync(SETTINGS_FILE)) {
|
||||
fs.unlinkSync(SETTINGS_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
test('auth/status reports the configured defaultBaseUrl', async () => {
|
||||
fs.writeFileSync(SETTINGS_FILE, JSON.stringify({
|
||||
gitProviders: { gitea: { apiBaseUrl: 'https://gitea.example.com' } },
|
||||
}));
|
||||
setGiteaAuth({ accessToken: 'gitea-a', baseUrl: 'https://gitea.example.com', user: aliceUser });
|
||||
scriptedFetch([(url) => (matches(/\/api\/v1\/user$/)(url) ? jsonResponse(aliceUser) : null)]);
|
||||
|
||||
const app = createApp();
|
||||
const response = await request(app).get('/api/gitea/auth/status');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.connected).toBe(true);
|
||||
expect(response.body.defaultBaseUrl).toBe('https://gitea.example.com');
|
||||
});
|
||||
|
||||
test('auth/connect falls back to the configured default base URL when baseUrl is blank', async () => {
|
||||
fs.writeFileSync(SETTINGS_FILE, JSON.stringify({
|
||||
gitProviders: { gitea: { apiBaseUrl: 'https://gitea.example.com' } },
|
||||
}));
|
||||
const fetchMock = scriptedFetch([(url) => (matches(/\/api\/v1\/user$/)(url) ? jsonResponse(aliceUser) : null)]);
|
||||
|
||||
const app = createApp();
|
||||
const response = await request(app)
|
||||
.post('/api/gitea/auth/connect')
|
||||
.send({ accessToken: 'gitea-valid' });
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toMatchObject({ connected: true });
|
||||
expect(fetchMock.mock.calls[0][0]).toBe('https://gitea.example.com/api/v1/user');
|
||||
expect(getGiteaAuth()?.baseUrl).toBe('https://gitea.example.com');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Gitea data routes', () => {
|
||||
beforeEach(() => {
|
||||
resetAuthFile();
|
||||
|
||||
Reference in New Issue
Block a user