mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-30 21:23:04 +00:00
* refactor(forge): forge-neutral foundation (GitHub-only) Decouple git-hosting from GitHub behind a neutral abstraction (issue #1616), GitHub-only for now; existing GitHub behaviour is unchanged. - Forge manifest, neutral ForgeService contract, forge registry + resolver, and a client forge-module registry. - GitHub code renamed to the neutral shape; PR/Issue attachment wording preserved. - forge.search.response enums parse tolerantly (unknown kind/auth state degrade instead of breaking the client). - createPullRequest reports typed CLI/auth errors instead of a generic message. - forge-resolver host/remote caches are LRU-bounded. - Forge host trust is explicit: only a known cloud host or a CLI-authenticated host is ever talked to; an unauthenticated GitHub Enterprise host fails resolution instead of routing to github.com. - Docs: forge-providers guide, glossary and i18n forge-copy conventions, architecture and rpc-namespacing terminology. - Vitest React Native mocks (unistyles, svg, linking, lucide) consolidated into shared aliased test-stubs. * feat(forge): GitLab adapter, forge-aware UI, pipelines and approvals GitLab adapter over the glab CLI on the neutral contracts: MR status, forge-aware UI, pipeline tree, and N-of-M approvals. - threadIsResolved is part of the neutral timeline item. - Pipeline load failures show an error instead of an empty section. - Manual pipeline jobs render as pending. - Fork/detached MR head pipelines are fetched by MR iid (glab ci get --merge-request). * feat(forge): Gitea family adapter (Gitea, Forgejo, Codeberg) One adapter over the tea CLI serving Gitea, Forgejo, and Codeberg on the neutral contracts. - CI status aggregates commit statuses and Actions runs together. - Gitea's terminal "warning" state maps to failure on server and client. - Gitea Actions check details are reachable from the PR pane by workflowRunId. * refactor(forge): localize compatibility handling * test(forge): expect normalized GitLab facts --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
283 lines
7.6 KiB
TypeScript
283 lines
7.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { buildForgeSearchQueryOptions, forgeSearchQueryKey } from "./use-forge-search-query";
|
|
|
|
describe("forgeSearchQueryKey", () => {
|
|
it("keeps the shared cache key shape for no-kinds searches", () => {
|
|
expect(forgeSearchQueryKey("server-1", "/repo", " 123 ")).toEqual([
|
|
"forge-search",
|
|
"server-1",
|
|
"/repo",
|
|
"forge",
|
|
"123",
|
|
]);
|
|
});
|
|
|
|
it("adds a deterministic kinds key when kinds are specified", () => {
|
|
expect(forgeSearchQueryKey("server-1", "/repo", "123", ["change_request", "issue"])).toEqual([
|
|
"forge-search",
|
|
"server-1",
|
|
"/repo",
|
|
"forge",
|
|
"123",
|
|
"change_request,issue",
|
|
]);
|
|
});
|
|
|
|
it("separates legacy GitHub fallback results from forge search results", () => {
|
|
expect(forgeSearchQueryKey("server-1", "/repo", "123", undefined, "github")).toEqual([
|
|
"forge-search",
|
|
"server-1",
|
|
"/repo",
|
|
"github",
|
|
"123",
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("buildForgeSearchQueryOptions", () => {
|
|
it("forwards kinds to the forge search request when specified", async () => {
|
|
const requests: unknown[] = [];
|
|
const query = buildForgeSearchQueryOptions({
|
|
client: {
|
|
async searchForge(options) {
|
|
requests.push(options);
|
|
return {
|
|
items: [],
|
|
authState: "authenticated",
|
|
error: null,
|
|
requestId: "request-1",
|
|
};
|
|
},
|
|
},
|
|
serverId: "server-1",
|
|
cwd: "/repo",
|
|
query: " 123 ",
|
|
kinds: ["change_request"],
|
|
enabled: true,
|
|
});
|
|
|
|
await query.queryFn();
|
|
|
|
expect(requests).toEqual([
|
|
{ cwd: "/repo", query: "123", limit: 20, kinds: ["change_request"] },
|
|
]);
|
|
});
|
|
|
|
it("uses the legacy GitHub search request when forge search is unsupported", async () => {
|
|
const forgeRequests: unknown[] = [];
|
|
const githubRequests: unknown[] = [];
|
|
const query = buildForgeSearchQueryOptions({
|
|
client: {
|
|
async searchForge(options) {
|
|
forgeRequests.push(options);
|
|
return {
|
|
items: [],
|
|
authState: "authenticated",
|
|
error: null,
|
|
requestId: "forge-request",
|
|
};
|
|
},
|
|
async searchGitHub(options) {
|
|
githubRequests.push(options);
|
|
return {
|
|
items: [],
|
|
featuresEnabled: true,
|
|
authState: "authenticated",
|
|
githubFeaturesEnabled: true,
|
|
error: null,
|
|
requestId: "github-request",
|
|
};
|
|
},
|
|
},
|
|
serverId: "server-1",
|
|
cwd: "/repo",
|
|
query: " 456 ",
|
|
kinds: ["issue"],
|
|
enabled: true,
|
|
supportsForgeSearch: false,
|
|
});
|
|
|
|
await query.queryFn();
|
|
|
|
expect(forgeRequests).toEqual([]);
|
|
expect(githubRequests).toEqual([
|
|
{ cwd: "/repo", query: "456", limit: 20, kinds: ["github-issue"] },
|
|
]);
|
|
});
|
|
|
|
it("normalizes legacy GitHub PR items into neutral change-request items", async () => {
|
|
const query = buildForgeSearchQueryOptions({
|
|
client: {
|
|
async searchForge() {
|
|
throw new Error("unexpected forge search");
|
|
},
|
|
async searchGitHub() {
|
|
return {
|
|
items: [
|
|
{
|
|
kind: "pr" as const,
|
|
number: 17,
|
|
title: "Fix search",
|
|
url: "https://github.com/acme/repo/pull/17",
|
|
state: "open",
|
|
body: null,
|
|
labels: ["bug"],
|
|
baseRefName: "main",
|
|
headRefName: "fix-search",
|
|
},
|
|
],
|
|
featuresEnabled: true,
|
|
authState: "authenticated" as const,
|
|
githubFeaturesEnabled: true,
|
|
error: null,
|
|
requestId: "github-request",
|
|
};
|
|
},
|
|
},
|
|
serverId: "server-1",
|
|
cwd: "/repo",
|
|
query: " 456 ",
|
|
enabled: true,
|
|
supportsForgeSearch: false,
|
|
});
|
|
|
|
const result = await query.queryFn();
|
|
|
|
expect(result.items).toEqual([
|
|
{
|
|
kind: "change_request",
|
|
number: 17,
|
|
title: "Fix search",
|
|
url: "https://github.com/acme/repo/pull/17",
|
|
state: "open",
|
|
body: null,
|
|
labels: ["bug"],
|
|
baseRefName: "main",
|
|
headRefName: "fix-search",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("interprets modern search payloads at the query boundary", async () => {
|
|
const query = buildForgeSearchQueryOptions({
|
|
client: {
|
|
async searchForge() {
|
|
return {
|
|
items: [
|
|
{
|
|
kind: "issue",
|
|
number: 23,
|
|
title: "Keep this",
|
|
url: "https://gitlab.com/acme/repo/-/issues/23",
|
|
state: "open",
|
|
body: null,
|
|
labels: [],
|
|
},
|
|
{ kind: "future_kind", futureField: true },
|
|
],
|
|
authState: "future_auth_state",
|
|
error: null,
|
|
requestId: "forge-request",
|
|
};
|
|
},
|
|
},
|
|
serverId: "server-1",
|
|
cwd: "/repo",
|
|
query: "23",
|
|
enabled: true,
|
|
supportsForgeSearch: true,
|
|
});
|
|
|
|
const result = await query.queryFn();
|
|
|
|
expect(result.items).toHaveLength(1);
|
|
expect(result.items[0]?.kind).toBe("issue");
|
|
expect(result.authState).toBe("unauthenticated");
|
|
});
|
|
|
|
it("derives legacy search auth from legacy feature flags", async () => {
|
|
const query = buildForgeSearchQueryOptions({
|
|
client: {
|
|
async searchForge() {
|
|
throw new Error("unexpected forge search");
|
|
},
|
|
async searchGitHub() {
|
|
return {
|
|
items: [],
|
|
githubFeaturesEnabled: false,
|
|
error: null,
|
|
requestId: "github-request",
|
|
};
|
|
},
|
|
},
|
|
serverId: "server-1",
|
|
cwd: "/repo",
|
|
query: "23",
|
|
enabled: true,
|
|
supportsForgeSearch: false,
|
|
});
|
|
|
|
expect((await query.queryFn()).authState).toBe("unauthenticated");
|
|
});
|
|
|
|
it("invokes forge search bound to the client so this-dependent methods work", async () => {
|
|
const client = new ThisDependentSearchClient();
|
|
|
|
const query = buildForgeSearchQueryOptions({
|
|
client,
|
|
serverId: "server-1",
|
|
cwd: "/repo",
|
|
query: " 789 ",
|
|
enabled: true,
|
|
supportsForgeSearch: true,
|
|
});
|
|
|
|
const result = await query.queryFn();
|
|
|
|
expect(result.requestId).toBe("forge.search.request");
|
|
expect(client.requests).toEqual([{ cwd: "/repo", query: "789", limit: 20 }]);
|
|
});
|
|
|
|
it("invokes the legacy GitHub search bound to the client", async () => {
|
|
const client = new ThisDependentSearchClient();
|
|
|
|
const query = buildForgeSearchQueryOptions({
|
|
client,
|
|
serverId: "server-1",
|
|
cwd: "/repo",
|
|
query: " 789 ",
|
|
enabled: true,
|
|
supportsForgeSearch: false,
|
|
});
|
|
|
|
const result = await query.queryFn();
|
|
|
|
expect(result.requestId).toBe("github_search_request");
|
|
expect(client.requests).toEqual([{ cwd: "/repo", query: "789", limit: 20 }]);
|
|
});
|
|
});
|
|
|
|
class ThisDependentSearchClient {
|
|
readonly requests: unknown[] = [];
|
|
|
|
private send(requestId: string, options: { cwd: string; query: string; limit?: number }) {
|
|
this.requests.push(options);
|
|
return Promise.resolve({
|
|
items: [],
|
|
featuresEnabled: true,
|
|
authState: "authenticated" as const,
|
|
githubFeaturesEnabled: true,
|
|
error: null,
|
|
requestId,
|
|
});
|
|
}
|
|
|
|
async searchForge(options: { cwd: string; query: string; limit?: number }) {
|
|
return this.send("forge.search.request", options);
|
|
}
|
|
|
|
async searchGitHub(options: { cwd: string; query: string; limit?: number }) {
|
|
return this.send("github_search_request", options);
|
|
}
|
|
}
|