/**
import fs from "fs";
* Token 认证测试
* 测试 Token 的获取、验证、更新等流程
*/
/// <reference path="../global.d.ts" />
import { exec } from 'child_process';
/// <reference path="../global.d.ts" />
import { validateTokenFormat } from '../setup';
/// <reference path="../global.d.ts" />
import { ZenTaoClient } from '../../src/client';
describe('Token Authentication', () => {
const envPath = '.env';
describe('Token Format Validation', () => {
test('should have valid token format in .env', () => {
const envContent = fs.readFileSync(envPath, 'utf8');
const tokenMatch = envContent.match(/ZENDTAO_TOKEN=(.+)/);
expect(tokenMatch).toBeTruthy();
expect(tokenMatch![1]).toBeTruthy();
const token = tokenMatch![1].trim();
expect(validateTokenFormat(token)).toBe(true);
console.log(`✅ Token format is valid: ${token.substring(0, 20)}...`);
});
test('should match expected format (sXXXX:32charMD5)', () => {
const envContent = fs.readFileSync(envPath, 'utf8');
const tokenMatch = envContent.match(/ZENDTAO_TOKEN=(.+)/);
expect(tokenMatch).toBeTruthy();
const token = tokenMatch![1].trim();
expect(token).toMatch(/^s\d+:[a-f0-9]{32}$/i);
console.log(`✅ Token matches expected pattern`);
});
test('should have account prefix (sXXXX)', () => {
const envContent = fs.readFileSync(envPath, 'utf8');
const tokenMatch = envContent.match(/ZENDTAO_TOKEN=(.+)/);
expect(tokenMatch).toBeTruthy();
const token = tokenMatch![1].trim();
const [account, hash] = token.split(':');
expect(account).toMatch(/^s\d+$/);
expect(hash).toHaveLength(32);
console.log(`✅ Token has valid account prefix: ${account}`);
});
test('should have MD5 hash (32 hex characters)', () => {
const envContent = fs.readFileSync(envPath, 'utf8');
const tokenMatch = envContent.match(/ZENDTAO_TOKEN=(.+)/);
expect(tokenMatch).toBeTruthy();
const token = tokenMatch![1].trim();
const [, hash] = token.split(':');
expect(hash).toMatch(/^[a-f0-9]{32}$/i);
console.log(`✅ Token has valid MD5 hash`);
});
});
describe('Token Validity', () => {
test('should be able to authenticate with token', async () => {
if (!global.testConfig.token) {
console.log('⏭️ Skipping - no token available');
return;
}
const client = new ZenTaoClient(global.testConfig, global.testLogger);
const isConnected = await client.verifyConnection();
expect(isConnected).toBe(true);
console.log('✅ Token authentication successful');
});
test('should be able to fetch data with token', async () => {
if (!global.testConfig.token) {
console.log('⏭️ Skipping - no token available');
return;
}
const client = new ZenTaoClient(global.testConfig, global.testLogger);
const projects = await client.get('/api.php/v1/projects', { limit: 1 });
expect(projects).toBeDefined();
expect(projects).toHaveProperty('projects');
expect(Array.isArray(projects.projects)).toBe(true);
console.log('✅ Token can fetch data from API');
});
test('should get version information', async () => {
if (!global.testConfig.token) {
console.log('⏭️ Skipping - no token available');
return;
}
const version = await global.testClient.getVersion();
expect(version).toBeTruthy();
expect(typeof version).toBe('string');
console.log(`✅ Retrieved version: ${version}`);
});
});
describe('Token Update Script', () => {
test('should have update-token.sh script', () => {
expect(fs.existsSync('./update-token.sh')).toBe(true);
expect(fs.statSync('./update-token.sh').mode & 0o111).not.toBe(0);
console.log('✅ update-token.sh script exists and is executable');
});
test('should have diagnose-token.sh script', () => {
expect(fs.existsSync('./diagnose-token.sh')).toBe(true);
expect(fs.statSync('./diagnose-token.sh').mode & 0o111).not.toBe(0);
console.log('✅ diagnose-token.sh script exists and is executable');
});
test('diagnose-token.sh should validate current token', (done) => {
exec('./diagnose-token.sh', (error, stdout, stderr) => {
if (error) {
console.log(`⏭️ Token diagnosis failed (expected if token expired): ${error.message}`);
}
const output = stdout + stderr;
expect(output).toContain('Token 诊断');
expect(output).toContain('格式检查');
console.log('✅ diagnose-token.sh runs successfully');
done();
});
}, 15000);
});
describe('Invalid Token Handling', () => {
test('should reject invalid token', async () => {
const invalidClient = new ZenTaoClient({
baseUrl: global.testConfig.baseUrl,
token: 's000001:invalid_token_hash_xxxxxxxxxxxxxxxxx',
timeout: global.testConfig.timeout,
retry: global.testConfig.retry,
retryDelay: global.testConfig.retryDelay
}, global.testLogger);
await expect(invalidClient.verifyConnection())
.rejects
.toThrow();
console.log('✅ Invalid token correctly rejected');
});
test('should reject empty token', async () => {
const emptyClient = new ZenTaoClient({
baseUrl: global.testConfig.baseUrl,
token: '',
timeout: global.testConfig.timeout,
retry: global.testConfig.retry,
retryDelay: global.testConfig.retryDelay
}, global.testLogger);
await expect(emptyClient.verifyConnection())
.rejects
.toThrow();
console.log('✅ Empty token correctly rejected');
});
test('should reject malformed token', async () => {
const malformedClient = new ZenTaoClient({
baseUrl: global.testConfig.baseUrl,
token: 'malformed_token',
timeout: global.testConfig.timeout,
retry: global.testConfig.retry,
retryDelay: global.testConfig.retryDelay
}, global.testLogger);
await expect(malformedClient.verifyConnection())
.rejects
.toThrow();
console.log('✅ Malformed token correctly rejected');
});
});
describe('Token Expiration', () => {
test('should detect expired token', async () => {
if (!global.testConfig.token) {
console.log('⏭️ Skipping - no token available');
return;
}
const expiredToken = 's000001:00000000000000000000000000000000';
const expiredClient = new ZenTaoClient({
baseUrl: global.testConfig.baseUrl,
token: expiredToken,
timeout: global.testConfig.timeout,
retry: 1,
retryDelay: 1000
}, global.testLogger);
await expect(expiredClient.verifyConnection())
.rejects
.toThrow();
console.log('✅ Expired token correctly detected');
}, 5000);
test('should show appropriate error for expired token', async () => {
if (!global.testConfig.token) {
console.log('⏭️ Skipping - no token available');
return;
}
const expiredToken = 's000001:expired_hash_xxxxxxxxxxxxxxxxxxxxxx';
const expiredClient = new ZenTaoClient({
baseUrl: global.testConfig.baseUrl,
token: expiredToken,
timeout: global.testConfig.timeout,
retry: 1,
retryDelay: 1000
}, global.testLogger);
try {
await expiredClient.verifyConnection();
throw new Error('Should have thrown error');
} catch (error: any) {
expect(error.message).toMatch(/unauthorized|401|Unauthorized/i);
console.log(`✅ Expired token error message: ${error.message}`);
}
}, 5000);
});
describe('Token Security', () => {
test('should not log sensitive token information', async () => {
if (!global.testConfig.token) {
console.log('⏭️ Skipping - no token available');
return;
}
const testLogger = {
info: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
warn: jest.fn()
};
const client = new ZenTaoClient(global.testConfig, testLogger as any);
await client.verifyConnection();
// 检查日志中不应包含完整的 token
const logCalls = [
...testLogger.info.mock.calls,
...testLogger.debug.mock.calls,
...testLogger.error.mock.calls,
...testLogger.warn.mock.calls
];
const allLogs = logCalls.flat().join(' ').toLowerCase();
// Token 的 MD5 部分(32字符)不应该完整出现在日志中
const tokenHash = global.testConfig.token.split(':')[1];
const tokenHashInLogs = allLogs.includes(tokenHash.toLowerCase());
expect(tokenHashInLogs).toBe(false);
console.log('✅ Token hash not exposed in logs');
});
});
});