mirror of
https://github.com/FoggedLens/deflock.git
synced 2026-07-14 15:57:20 +02:00
34e2cbdc2d
Extends the previous commit's fix. The only caller (Donate.vue via apiService.getSponsors()) never sends a username and always relied on the server-side default, so the parameter had no legitimate use even after being parameterized — it just let anyone use deflock's server as an unauthenticated relay onto GitHub's GraphQL API under the server's own token. Drop it entirely: the route no longer accepts a username querystring param, and GithubClient.getSponsors() takes no argument, hardcoding the maintainer's login via MAINTAINER_USERNAME. Keeps the $username GraphQL-variable parameterization technique so reintroducing a variable input later can't reintroduce the injection. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
import { describe, it, expect, afterEach, mock } from 'bun:test';
|
|
import { GithubClient, buildSponsorsQuery } from './GithubClient';
|
|
|
|
describe('buildSponsorsQuery', () => {
|
|
it('parameterizes the username via a $username GraphQL variable instead of interpolating it', () => {
|
|
const { query, variables } = buildSponsorsQuery();
|
|
expect(query).toContain('query($username: String!)');
|
|
expect(query).toContain('user(login: $username)');
|
|
expect(variables).toEqual({ username: 'frillweeman' });
|
|
});
|
|
|
|
it('requests sponsorships and the expected sponsor fields', () => {
|
|
const { query } = buildSponsorsQuery();
|
|
expect(query).toContain('sponsorshipsAsMaintainer(first: 100)');
|
|
for (const field of ['login', 'name', 'avatarUrl', 'url']) {
|
|
expect(query).toContain(field);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('GithubClient.getSponsors', () => {
|
|
const originalFetch = global.fetch;
|
|
|
|
afterEach(() => {
|
|
global.fetch = originalFetch;
|
|
});
|
|
|
|
it('sends exactly the query and variables built by buildSponsorsQuery', async () => {
|
|
let capturedBody: any;
|
|
global.fetch = mock(async (_url: string, init: RequestInit) => {
|
|
capturedBody = JSON.parse(init.body as string);
|
|
return new Response(JSON.stringify({ data: { user: { sponsorshipsAsMaintainer: { nodes: [] } } } }));
|
|
}) as unknown as typeof fetch;
|
|
|
|
const client = new GithubClient();
|
|
await client.getSponsors();
|
|
|
|
expect(capturedBody).toEqual(buildSponsorsQuery());
|
|
});
|
|
|
|
it('returns the sponsor nodes from the response', async () => {
|
|
const nodes = [{ sponsor: { login: 'someone', name: 'Someone', avatarUrl: 'https://example.com/a.png', url: 'https://github.com/someone' } }];
|
|
global.fetch = mock(async () =>
|
|
new Response(JSON.stringify({ data: { user: { sponsorshipsAsMaintainer: { nodes } } } }))
|
|
) as unknown as typeof fetch;
|
|
|
|
const client = new GithubClient();
|
|
const result = await client.getSponsors();
|
|
|
|
expect(result).toEqual(nodes);
|
|
});
|
|
|
|
it('throws when the GitHub API responds with a non-OK status', async () => {
|
|
global.fetch = mock(async () => new Response('', { status: 500 })) as unknown as typeof fetch;
|
|
|
|
const client = new GithubClient();
|
|
|
|
await expect(client.getSponsors()).rejects.toThrow('Failed to get sponsors: 500');
|
|
});
|
|
}); |