tests: avoid global fetch mutation in GitHub device flow tests (#702)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
|
||||
import { afterEach, describe, expect, mock, test } from 'bun:test'
|
||||
|
||||
import {
|
||||
DEFAULT_GITHUB_DEVICE_SCOPE,
|
||||
@@ -12,22 +12,15 @@ async function importFreshModule() {
|
||||
return import(`./deviceFlow.ts?ts=${Date.now()}-${Math.random()}`)
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore()
|
||||
})
|
||||
|
||||
describe('requestDeviceCode', () => {
|
||||
const originalFetch = globalThis.fetch
|
||||
|
||||
beforeEach(() => {
|
||||
mock.restore()
|
||||
globalThis.fetch = originalFetch
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch
|
||||
})
|
||||
|
||||
test('parses successful device code response', async () => {
|
||||
const { requestDeviceCode } = await importFreshModule()
|
||||
|
||||
globalThis.fetch = mock(() =>
|
||||
const fetchImpl = mock(() =>
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
@@ -44,7 +37,7 @@ describe('requestDeviceCode', () => {
|
||||
|
||||
const r = await requestDeviceCode({
|
||||
clientId: 'test-client',
|
||||
fetchImpl: globalThis.fetch,
|
||||
fetchImpl,
|
||||
})
|
||||
expect(r.device_code).toBe('abc')
|
||||
expect(r.user_code).toBe('ABCD-1234')
|
||||
@@ -57,17 +50,17 @@ describe('requestDeviceCode', () => {
|
||||
const { requestDeviceCode, GitHubDeviceFlowError } =
|
||||
await importFreshModule()
|
||||
|
||||
globalThis.fetch = mock(() =>
|
||||
const fetchImpl = mock(() =>
|
||||
Promise.resolve(new Response('bad', { status: 500 })),
|
||||
)
|
||||
await expect(
|
||||
requestDeviceCode({ clientId: 'x', fetchImpl: globalThis.fetch }),
|
||||
requestDeviceCode({ clientId: 'x', fetchImpl }),
|
||||
).rejects.toThrow(GitHubDeviceFlowError)
|
||||
})
|
||||
|
||||
test('uses OAuth-safe default scope', async () => {
|
||||
let capturedScope = ''
|
||||
globalThis.fetch = mock((_url: RequestInfo | URL, init?: RequestInit) => {
|
||||
const fetchImpl = mock((_url: RequestInfo | URL, init?: RequestInit) => {
|
||||
const body = init?.body
|
||||
if (body instanceof URLSearchParams) {
|
||||
capturedScope = body.get('scope') ?? ''
|
||||
@@ -87,7 +80,7 @@ describe('requestDeviceCode', () => {
|
||||
)
|
||||
})
|
||||
|
||||
await requestDeviceCode({ clientId: 'test-client', fetchImpl: globalThis.fetch })
|
||||
await requestDeviceCode({ clientId: 'test-client', fetchImpl })
|
||||
expect(capturedScope).toBe(DEFAULT_GITHUB_DEVICE_SCOPE)
|
||||
expect(capturedScope).toBe('read:user')
|
||||
})
|
||||
@@ -96,7 +89,7 @@ describe('requestDeviceCode', () => {
|
||||
const scopesSeen: string[] = []
|
||||
let callCount = 0
|
||||
|
||||
globalThis.fetch = mock((_url: RequestInfo | URL, init?: RequestInit) => {
|
||||
const fetchImpl = mock((_url: RequestInfo | URL, init?: RequestInit) => {
|
||||
const body = init?.body
|
||||
const scope =
|
||||
body instanceof URLSearchParams
|
||||
@@ -132,7 +125,7 @@ describe('requestDeviceCode', () => {
|
||||
const result = await requestDeviceCode({
|
||||
clientId: 'test-client',
|
||||
scope: 'read:user,models:read',
|
||||
fetchImpl: globalThis.fetch,
|
||||
fetchImpl,
|
||||
})
|
||||
|
||||
expect(result.device_code).toBe('abc')
|
||||
@@ -142,17 +135,11 @@ describe('requestDeviceCode', () => {
|
||||
})
|
||||
|
||||
describe('pollAccessToken', () => {
|
||||
const originalFetch = globalThis.fetch
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch
|
||||
})
|
||||
|
||||
test('returns token when GitHub responds with access_token immediately', async () => {
|
||||
const { pollAccessToken } = await importFreshModule()
|
||||
|
||||
let calls = 0
|
||||
globalThis.fetch = mock(() => {
|
||||
const fetchImpl = mock(() => {
|
||||
calls++
|
||||
return Promise.resolve(
|
||||
new Response(JSON.stringify({ access_token: 'tok-xyz' }), {
|
||||
@@ -163,7 +150,7 @@ describe('pollAccessToken', () => {
|
||||
|
||||
const token = await pollAccessToken('dev-code', {
|
||||
clientId: 'cid',
|
||||
fetchImpl: globalThis.fetch,
|
||||
fetchImpl,
|
||||
})
|
||||
expect(token).toBe('tok-xyz')
|
||||
expect(calls).toBe(1)
|
||||
@@ -172,7 +159,7 @@ describe('pollAccessToken', () => {
|
||||
test('throws on access_denied', async () => {
|
||||
const { pollAccessToken } = await importFreshModule()
|
||||
|
||||
globalThis.fetch = mock(() =>
|
||||
const fetchImpl = mock(() =>
|
||||
Promise.resolve(
|
||||
new Response(JSON.stringify({ error: 'access_denied' }), {
|
||||
status: 200,
|
||||
@@ -182,23 +169,17 @@ describe('pollAccessToken', () => {
|
||||
await expect(
|
||||
pollAccessToken('dc', {
|
||||
clientId: 'c',
|
||||
fetchImpl: globalThis.fetch,
|
||||
fetchImpl,
|
||||
}),
|
||||
).rejects.toThrow(/denied/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('exchangeForCopilotToken', () => {
|
||||
const originalFetch = globalThis.fetch
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch
|
||||
})
|
||||
|
||||
test('parses successful Copilot token response', async () => {
|
||||
const { exchangeForCopilotToken } = await importFreshModule()
|
||||
|
||||
globalThis.fetch = mock(() =>
|
||||
const fetchImpl = mock(() =>
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
@@ -214,7 +195,7 @@ describe('exchangeForCopilotToken', () => {
|
||||
),
|
||||
)
|
||||
|
||||
const result = await exchangeForCopilotToken('oauth-token', globalThis.fetch)
|
||||
const result = await exchangeForCopilotToken('oauth-token', fetchImpl)
|
||||
expect(result.token).toBe('copilot-token-xyz')
|
||||
expect(result.expires_at).toBe(1700000000)
|
||||
expect(result.refresh_in).toBe(3600)
|
||||
@@ -225,24 +206,24 @@ describe('exchangeForCopilotToken', () => {
|
||||
const { exchangeForCopilotToken, GitHubDeviceFlowError } =
|
||||
await importFreshModule()
|
||||
|
||||
globalThis.fetch = mock(() =>
|
||||
const fetchImpl = mock(() =>
|
||||
Promise.resolve(new Response('unauthorized', { status: 401 })),
|
||||
)
|
||||
await expect(
|
||||
exchangeForCopilotToken('bad-token', globalThis.fetch),
|
||||
exchangeForCopilotToken('bad-token', fetchImpl),
|
||||
).rejects.toThrow(GitHubDeviceFlowError)
|
||||
})
|
||||
|
||||
test('throws on malformed response', async () => {
|
||||
const { exchangeForCopilotToken } = await importFreshModule()
|
||||
|
||||
globalThis.fetch = mock(() =>
|
||||
const fetchImpl = mock(() =>
|
||||
Promise.resolve(
|
||||
new Response(JSON.stringify({ invalid: 'data' }), { status: 200 }),
|
||||
),
|
||||
)
|
||||
await expect(
|
||||
exchangeForCopilotToken('oauth-token', globalThis.fetch),
|
||||
exchangeForCopilotToken('oauth-token', fetchImpl),
|
||||
).rejects.toThrow(/Malformed/)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user