fix(dashboard): honor forced GraphQL transport (#2353)
## Summary - honor `forceMode` for GraphQL queries and mutations - keep token-backed Discussion operations on the selected token transport even when `gh` is authenticated - add regression coverage for forced-token category lookup, errors, and Discussion creation ## Test plan - `CI=true pnpm --filter @fusion/dashboard exec vitest run src/__tests__/github-discussions.test.ts src/__tests__/github-forced-mode.test.ts src/__tests__/github-pr-threads.test.ts` - `CI=true pnpm build` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * GitHub Discussions GraphQL operations now consistently honor the selected transport/authentication mode. * Forced token-based transport correctly applies to discussion queries and mutations (including discussion creation). * Missing required authentication is now reported instead of falling back to an unauthenticated path. * **Tests** * Added a test covering discussion creation using forced token-based GraphQL transport. * **Release** * Shipped as a patch release of the Fusion library. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
7
.changeset/fix-github-discussions-force-mode.md
Normal file
7
.changeset/fix-github-discussions-force-mode.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Honor forced GitHub transport selection for GraphQL discussion queries and mutations.
|
||||||
|
category: fix
|
||||||
|
dev: Keep Discussion GraphQL operations on the explicitly selected token or gh CLI transport.
|
||||||
@@ -31,6 +31,23 @@ describe("GitHub Discussions GraphQL transport", () => {
|
|||||||
.rejects.toThrow("Resource not accessible by personal access token");
|
.rejects.toThrow("Resource not accessible by personal access token");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("creates a discussion through the forced token GraphQL transport", async () => {
|
||||||
|
const fetchMock = vi.fn()
|
||||||
|
.mockResolvedValueOnce(new Response(JSON.stringify({
|
||||||
|
data: { repository: { id: "R_1", discussionCategories: { nodes: [{ id: "DC_1" }] } } },
|
||||||
|
}), { status: 200 }))
|
||||||
|
.mockResolvedValueOnce(new Response(JSON.stringify({
|
||||||
|
data: { createDiscussion: { discussion: { id: "D_1", number: 42, url: "https://github.com/Runfusion/Fusion/discussions/42" } } },
|
||||||
|
}), { status: 200 }));
|
||||||
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
|
||||||
|
const discussion = await new GitHubClient({ token: "test", forceMode: "token" })
|
||||||
|
.createDiscussion("Runfusion", "Fusion", "Title", "Body", "DC_1");
|
||||||
|
|
||||||
|
expect(discussion).toEqual({ id: "D_1", number: 42, htmlUrl: "https://github.com/Runfusion/Fusion/discussions/42" });
|
||||||
|
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||||
|
expect(fetchMock.mock.calls.every(([url]) => String(url) === "https://api.github.com/graphql")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("maps disabled Discussions errors from search to a typed signal", async () => {
|
it("maps disabled Discussions errors from search to a typed signal", async () => {
|
||||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(JSON.stringify({
|
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(JSON.stringify({
|
||||||
|
|||||||
@@ -2457,7 +2457,8 @@ export class GitHubClient {
|
|||||||
|
|
||||||
/** Run a read-only GraphQL query (gh CLI when available, else token/REST). */
|
/** Run a read-only GraphQL query (gh CLI when available, else token/REST). */
|
||||||
private async runGraphqlQuery<T>(query: string, variables: Record<string, string | number | null>): Promise<T | undefined> {
|
private async runGraphqlQuery<T>(query: string, variables: Record<string, string | number | null>): Promise<T | undefined> {
|
||||||
if (this.hasGhAuth()) {
|
if (this.forceMode === "gh-cli" || (this.forceMode === undefined && this.hasGhAuth())) {
|
||||||
|
if (this.forceMode === "gh-cli") this.requireGh();
|
||||||
const args = ["api", "graphql", "-f", `query=${query}`];
|
const args = ["api", "graphql", "-f", `query=${query}`];
|
||||||
for (const [key, value] of Object.entries(variables)) {
|
for (const [key, value] of Object.entries(variables)) {
|
||||||
if (value === null) continue;
|
if (value === null) continue;
|
||||||
@@ -2469,6 +2470,7 @@ export class GitHubClient {
|
|||||||
if (payload.errors?.length) throw new Error(payload.errors[0].message);
|
if (payload.errors?.length) throw new Error(payload.errors[0].message);
|
||||||
return payload.data;
|
return payload.data;
|
||||||
}
|
}
|
||||||
|
if (this.forceMode === "token") this.requireToken();
|
||||||
if (this.token) {
|
if (this.token) {
|
||||||
const response = await fetch(`${this.baseUrl}/graphql`, {
|
const response = await fetch(`${this.baseUrl}/graphql`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -2513,7 +2515,8 @@ export class GitHubClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async runGraphqlMutation(query: string, variables: Record<string, string>): Promise<void> {
|
private async runGraphqlMutation(query: string, variables: Record<string, string>): Promise<void> {
|
||||||
if (this.hasGhAuth()) {
|
if (this.forceMode === "gh-cli" || (this.forceMode === undefined && this.hasGhAuth())) {
|
||||||
|
if (this.forceMode === "gh-cli") this.requireGh();
|
||||||
const args = ["api", "graphql", "-f", `query=${query}`];
|
const args = ["api", "graphql", "-f", `query=${query}`];
|
||||||
for (const [key, value] of Object.entries(variables)) {
|
for (const [key, value] of Object.entries(variables)) {
|
||||||
args.push("-F", `${key}=${value}`);
|
args.push("-F", `${key}=${value}`);
|
||||||
@@ -2523,6 +2526,7 @@ export class GitHubClient {
|
|||||||
if (payload.errors?.length) throw new Error(payload.errors[0].message);
|
if (payload.errors?.length) throw new Error(payload.errors[0].message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.forceMode === "token") this.requireToken();
|
||||||
if (this.token) {
|
if (this.token) {
|
||||||
const response = await fetch(`${this.baseUrl}/graphql`, {
|
const response = await fetch(`${this.baseUrl}/graphql`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
Reference in New Issue
Block a user