210 lines
6.8 KiB
TypeScript
210 lines
6.8 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import * as smartai from '../ts/index.js';
|
|
import type { IOpenAiMaxTokenData } from '../ts/index.js';
|
|
|
|
interface IMockFetchRequest {
|
|
url: string;
|
|
init?: RequestInit;
|
|
}
|
|
|
|
function createJwt(payload: Record<string, unknown>): string {
|
|
const encode = (value: Record<string, unknown>) => Buffer.from(JSON.stringify(value)).toString('base64url');
|
|
return `${encode({ alg: 'none', typ: 'JWT' })}.${encode(payload)}.sig`;
|
|
}
|
|
|
|
function createTokenData(accountId = 'workspace-1'): IOpenAiMaxTokenData {
|
|
const idToken = createJwt({
|
|
email: 'user@example.com',
|
|
exp: 4_102_444_800,
|
|
'https://api.openai.com/auth': {
|
|
chatgpt_plan_type: 'pro',
|
|
chatgpt_user_id: 'user-1',
|
|
chatgpt_account_id: accountId,
|
|
chatgpt_account_is_fedramp: false,
|
|
},
|
|
});
|
|
const idTokenInfo = smartai.parseOpenAiMaxIdToken(idToken);
|
|
return {
|
|
idToken,
|
|
accessToken: 'access-token',
|
|
refreshToken: 'refresh-token',
|
|
accountId,
|
|
idTokenInfo,
|
|
};
|
|
}
|
|
|
|
function jsonResponse(body: unknown, status = 200): Response {
|
|
return new Response(JSON.stringify(body), {
|
|
status,
|
|
headers: { 'content-type': 'application/json' },
|
|
});
|
|
}
|
|
|
|
function getHeader(init: RequestInit | undefined, name: string): string | null {
|
|
return new Headers(init?.headers).get(name);
|
|
}
|
|
|
|
tap.test('requestOpenAiMaxDeviceCode requests a user code', async () => {
|
|
const originalFetch = globalThis.fetch;
|
|
const requests: IMockFetchRequest[] = [];
|
|
|
|
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
requests.push({ url: String(input), init });
|
|
return jsonResponse({
|
|
device_auth_id: 'device-1',
|
|
usercode: 'ABCD-EFGH',
|
|
interval: '2',
|
|
});
|
|
};
|
|
|
|
try {
|
|
const deviceCode = await smartai.requestOpenAiMaxDeviceCode({
|
|
issuer: 'https://auth.example.test',
|
|
clientId: 'client-1',
|
|
});
|
|
|
|
expect(deviceCode).toEqual({
|
|
verificationUrl: 'https://auth.example.test/codex/device',
|
|
userCode: 'ABCD-EFGH',
|
|
deviceAuthId: 'device-1',
|
|
intervalSeconds: 2,
|
|
});
|
|
expect(requests[0].url).toEqual('https://auth.example.test/api/accounts/deviceauth/usercode');
|
|
expect(JSON.parse(String(requests[0].init?.body))).toEqual({ client_id: 'client-1' });
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
tap.test('completeOpenAiMaxDeviceCodeLogin polls and exchanges OAuth tokens', async () => {
|
|
const originalFetch = globalThis.fetch;
|
|
const requests: IMockFetchRequest[] = [];
|
|
const tokenData = createTokenData('workspace-1');
|
|
const responses = [
|
|
jsonResponse({}, 403),
|
|
jsonResponse({
|
|
authorization_code: 'auth-code',
|
|
code_challenge: 'challenge',
|
|
code_verifier: 'verifier',
|
|
}),
|
|
jsonResponse({
|
|
id_token: tokenData.idToken,
|
|
access_token: tokenData.accessToken,
|
|
refresh_token: tokenData.refreshToken,
|
|
}),
|
|
];
|
|
|
|
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
requests.push({ url: String(input), init });
|
|
const response = responses.shift();
|
|
if (!response) throw new Error('Unexpected fetch call');
|
|
return response;
|
|
};
|
|
|
|
try {
|
|
const result = await smartai.completeOpenAiMaxDeviceCodeLogin({
|
|
verificationUrl: 'https://auth.example.test/codex/device',
|
|
userCode: 'ABCD-EFGH',
|
|
deviceAuthId: 'device-1',
|
|
intervalSeconds: 1,
|
|
}, {
|
|
issuer: 'https://auth.example.test',
|
|
clientId: 'client-1',
|
|
forcedChatGptWorkspaceId: 'workspace-1',
|
|
sleep: async () => undefined,
|
|
});
|
|
|
|
expect(result.accessToken).toEqual('access-token');
|
|
expect(result.refreshToken).toEqual('refresh-token');
|
|
expect(result.idTokenInfo.chatgptAccountId).toEqual('workspace-1');
|
|
expect(requests.length).toEqual(3);
|
|
expect(JSON.parse(String(requests[0].init?.body))).toEqual({
|
|
device_auth_id: 'device-1',
|
|
user_code: 'ABCD-EFGH',
|
|
});
|
|
const tokenExchangeBody = new URLSearchParams(String(requests[2].init?.body));
|
|
expect(tokenExchangeBody.get('grant_type')).toEqual('authorization_code');
|
|
expect(tokenExchangeBody.get('code')).toEqual('auth-code');
|
|
expect(tokenExchangeBody.get('redirect_uri')).toEqual('https://auth.example.test/deviceauth/callback');
|
|
expect(tokenExchangeBody.get('client_id')).toEqual('client-1');
|
|
expect(tokenExchangeBody.get('code_verifier')).toEqual('verifier');
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
tap.test('refreshOpenAiMaxTokenData refreshes and preserves omitted token fields', async () => {
|
|
const originalFetch = globalThis.fetch;
|
|
const requests: IMockFetchRequest[] = [];
|
|
const tokenData = createTokenData('workspace-1');
|
|
|
|
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
requests.push({ url: String(input), init });
|
|
return jsonResponse({ access_token: 'new-access-token' });
|
|
};
|
|
|
|
try {
|
|
const result = await smartai.refreshOpenAiMaxTokenData(tokenData, {
|
|
issuer: 'https://auth.example.test',
|
|
clientId: 'client-1',
|
|
});
|
|
|
|
expect(result.accessToken).toEqual('new-access-token');
|
|
expect(result.refreshToken).toEqual('refresh-token');
|
|
expect(result.idToken).toEqual(tokenData.idToken);
|
|
expect(JSON.parse(String(requests[0].init?.body))).toEqual({
|
|
client_id: 'client-1',
|
|
grant_type: 'refresh_token',
|
|
refresh_token: 'refresh-token',
|
|
});
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
tap.test('getModel uses ChatGPT Codex backend for OpenAI Max auth', async () => {
|
|
const originalFetch = globalThis.fetch;
|
|
let capturedRequest: IMockFetchRequest | undefined;
|
|
const tokenData = createTokenData('workspace-1');
|
|
const model = smartai.getModel({
|
|
provider: 'openai',
|
|
model: 'gpt-5.5',
|
|
openAiMaxAuth: tokenData,
|
|
});
|
|
|
|
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
capturedRequest = { url: String(input), init };
|
|
return jsonResponse({
|
|
id: 'resp-1',
|
|
created_at: 1,
|
|
model: 'gpt-5.5',
|
|
output: [{
|
|
type: 'message',
|
|
role: 'assistant',
|
|
id: 'msg-1',
|
|
content: [{ type: 'output_text', text: 'ok', annotations: [] }],
|
|
}],
|
|
usage: {
|
|
input_tokens: 1,
|
|
output_tokens: 1,
|
|
},
|
|
});
|
|
};
|
|
|
|
try {
|
|
await model.doGenerate({
|
|
prompt: [{ role: 'user', content: [{ type: 'text', text: 'hello' }] }],
|
|
inputFormat: 'prompt',
|
|
} as any);
|
|
|
|
expect(capturedRequest?.url).toEqual('https://chatgpt.com/backend-api/codex/responses');
|
|
expect(getHeader(capturedRequest?.init, 'authorization')).toEqual('Bearer access-token');
|
|
expect(getHeader(capturedRequest?.init, 'chatgpt-account-id')).toEqual('workspace-1');
|
|
expect(getHeader(capturedRequest?.init, 'originator')).toEqual('smartai');
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
export default tap.start();
|