Files
opencode-openai-codex-auth/test/auth.test.ts
T
Numman AliandHanzo Dev 7dae285752 feat: Major TypeScript rewrite with enhanced configuration and OAuth UI
## 🎯 Overview

Complete TypeScript rewrite with comprehensive testing, modular architecture,
and enhanced user experience. Adds 9 pre-configured model variants matching
Codex CLI presets and a beautiful OAuth success page.

## ✨ New Features

### Configuration System
- **9 Model Variants**: Pre-configured reasoning levels (Low/Medium/High) for
  both gpt-5-codex and gpt-5, plus Minimal variants
- **Full Config Example**: `config/full-opencode.json` with all variants ready
  to use
- **Minimal Config Example**: `config/minimal-opencode.json` for basic setup
- **Config Documentation**: Comprehensive guide in `config/README.md`

### OAuth Enhancement
- **Animated Success Page**: Beautiful terminal-style UI with matrix rain
  effect, animated checkmark, and glowing effects
- **Interactive Elements**: Mouse-responsive matrix rain and parallax effects
- **Professional Branding**: OpenCode logo with pulse animations

### Developer Experience
- **TypeScript Rewrite**: Complete migration from .mjs to .ts with strict typing
- **93 Comprehensive Tests**: Full test coverage using Vitest
- **Modular Architecture**: 10 focused helper functions, each < 40 lines
- **Centralized Constants**: All magic values in `lib/constants.ts`
- **CI/CD Pipeline**: GitHub Actions workflow for automated testing

### Documentation
- **AGENTS.md**: Complete guide for AI assistants maintaining the codebase
- **Enhanced README**: Updated with new configuration options and examples
- **Inline JSDoc**: Comprehensive documentation for all public functions

## 🏗️ Technical Changes

### Architecture Improvements
- Extracted 10 focused helper functions from monolithic fetch()
- Centralized all constants and error messages
- Separated concerns: auth, browser, codex, server, etc.
- Type-safe discriminated unions for result types

### Build System
- TypeScript compilation with declarations and source maps
- Automatic HTML asset copying in build process
- Test files excluded from distribution bundle
- Clean dist/ output with only production code

### Testing Infrastructure
- Vitest test runner with UI mode
- 93 tests across 7 test files
- Mock-based testing for external dependencies
- Coverage reporting capability

## 📁 New Structure

```
opencode-openai-codex-auth/
├── index.ts                     # Main plugin (TypeScript)
├── lib/
│   ├── auth.ts                 # OAuth logic
│   ├── browser.ts              # Platform-specific browser opening
│   ├── codex.ts                # Codex instructions
│   ├── server.ts               # OAuth callback server
│   ├── fetch-helpers.ts        # 10 focused helpers
│   ├── constants.ts            # All magic values
│   ├── types.ts                # TypeScript types
│   ├── oauth-success.html      # Animated success page
│   └── ...
├── config/
│   ├── full-opencode.json      # Complete config with variants
│   ├── minimal-opencode.json   # Basic config
│   └── README.md               # Config docs
├── test/                        # 93 comprehensive tests
├── AGENTS.md                    # AI maintenance guide
└── tsconfig.json               # TypeScript config
```

## 🔄 Migration Notes

### Breaking Changes
- Plugin now requires TypeScript compilation (dist/ folder)
- Configuration structure enhanced (backward compatible)
- OAuth success page replaces plain HTML response

### Upgrade Path
1. Use new config from `config/full-opencode.json` for best experience
2. Or continue with minimal config (uses plugin defaults)
3. Rebuild plugin: `npm run build`

## 📊 Metrics

- **Lines of Code**: Modular design with functions < 70 lines
- **Test Coverage**: 93 tests covering all functionality
- **Type Safety**: Strict TypeScript with no `any` types
- **Dependencies**: Still minimal (only @openauthjs/openauth)
- **Model Variants**: 9 pre-configured options for users

## 🎨 User Experience

- **Codex CLI Parity**: Match official CLI reasoning presets
- **Beautiful OAuth**: Professional animated success page
- **Clear Documentation**: Step-by-step guides and examples
- **Flexible Config**: From minimal to comprehensive setups

Co-authored-by: Hanzo Dev <[email protected]>
2025-10-03 21:51:59 +01:00

144 lines
4.7 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
createState,
parseAuthorizationInput,
decodeJWT,
createAuthorizationFlow,
CLIENT_ID,
AUTHORIZE_URL,
REDIRECT_URI,
SCOPE,
} from '../lib/auth.js';
describe('Auth Module', () => {
describe('createState', () => {
it('should generate a random 32-character hex string', () => {
const state = createState();
expect(state).toMatch(/^[a-f0-9]{32}$/);
});
it('should generate unique states', () => {
const state1 = createState();
const state2 = createState();
expect(state1).not.toBe(state2);
});
});
describe('parseAuthorizationInput', () => {
it('should parse full OAuth callback URL', () => {
const input = 'http://localhost:1455/auth/callback?code=abc123&state=xyz789';
const result = parseAuthorizationInput(input);
expect(result).toEqual({ code: 'abc123', state: 'xyz789' });
});
it('should parse code#state format', () => {
const input = 'abc123#xyz789';
const result = parseAuthorizationInput(input);
expect(result).toEqual({ code: 'abc123', state: 'xyz789' });
});
it('should parse query string format', () => {
const input = 'code=abc123&state=xyz789';
const result = parseAuthorizationInput(input);
expect(result).toEqual({ code: 'abc123', state: 'xyz789' });
});
it('should parse code only', () => {
const input = 'abc123';
const result = parseAuthorizationInput(input);
expect(result).toEqual({ code: 'abc123' });
});
it('should return empty object for empty input', () => {
const result = parseAuthorizationInput('');
expect(result).toEqual({});
});
it('should handle whitespace', () => {
const result = parseAuthorizationInput(' ');
expect(result).toEqual({});
});
});
describe('decodeJWT', () => {
it('should decode valid JWT token', () => {
// Create a simple JWT token: header.payload.signature
const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64');
const payload = Buffer.from(JSON.stringify({ sub: '1234567890', name: 'Test User' })).toString('base64');
const signature = 'fake-signature';
const token = `${header}.${payload}.${signature}`;
const decoded = decodeJWT(token);
expect(decoded).toEqual({ sub: '1234567890', name: 'Test User' });
});
it('should decode JWT with ChatGPT account info', () => {
const payload = Buffer.from(JSON.stringify({
'https://api.openai.com/auth': {
chatgpt_account_id: 'account-123',
},
})).toString('base64');
const token = `header.${payload}.signature`;
const decoded = decodeJWT(token);
expect(decoded?.['https://api.openai.com/auth']?.chatgpt_account_id).toBe('account-123');
});
it('should return null for invalid JWT', () => {
const result = decodeJWT('invalid-token');
expect(result).toBeNull();
});
it('should return null for malformed JWT', () => {
const result = decodeJWT('header.payload');
expect(result).toBeNull();
});
it('should return null for non-JSON payload', () => {
const token = 'header.not-json.signature';
const result = decodeJWT(token);
expect(result).toBeNull();
});
});
describe('createAuthorizationFlow', () => {
it('should create authorization flow with PKCE', async () => {
const flow = await createAuthorizationFlow();
expect(flow).toHaveProperty('pkce');
expect(flow).toHaveProperty('state');
expect(flow).toHaveProperty('url');
expect(flow.pkce).toHaveProperty('challenge');
expect(flow.pkce).toHaveProperty('verifier');
expect(flow.state).toMatch(/^[a-f0-9]{32}$/);
});
it('should generate URL with correct parameters', async () => {
const flow = await createAuthorizationFlow();
const url = new URL(flow.url);
expect(url.origin + url.pathname).toBe(AUTHORIZE_URL);
expect(url.searchParams.get('response_type')).toBe('code');
expect(url.searchParams.get('client_id')).toBe(CLIENT_ID);
expect(url.searchParams.get('redirect_uri')).toBe(REDIRECT_URI);
expect(url.searchParams.get('scope')).toBe(SCOPE);
expect(url.searchParams.get('code_challenge_method')).toBe('S256');
expect(url.searchParams.get('code_challenge')).toBe(flow.pkce.challenge);
expect(url.searchParams.get('state')).toBe(flow.state);
expect(url.searchParams.get('id_token_add_organizations')).toBe('true');
expect(url.searchParams.get('codex_cli_simplified_flow')).toBe('true');
expect(url.searchParams.get('originator')).toBe('codex_cli_rs');
});
it('should generate unique flows', async () => {
const flow1 = await createAuthorizationFlow();
const flow2 = await createAuthorizationFlow();
expect(flow1.state).not.toBe(flow2.state);
expect(flow1.pkce.verifier).not.toBe(flow2.pkce.verifier);
expect(flow1.url).not.toBe(flow2.url);
});
});
});