Files

255 lines
10 KiB
TypeScript

import 'reflect-metadata';
import { assert } from 'chai';
import * as sinon from 'sinon';
import { ProApiClient } from '../../../src/pro-api/ProApiClient';
import { IProApiConfig } from '../../../src/interfaces/pro-api/IProApiClient';
import { ApiError } from '../../../src/pro-api/ApiError';
describe('ProApiClient', () => {
const API_URL = 'https://obfuscator.io/api/v1/obfuscate';
let fetchStub: sinon.SinonStub;
beforeEach(() => {
fetchStub = sinon.stub(global, 'fetch');
});
afterEach(() => {
fetchStub.restore();
});
describe('constructor', () => {
describe('Variant #1: basic configuration', () => {
it('should create client with required apiToken', () => {
const config: IProApiConfig = {
apiToken: 'test-token'
};
const client = new ProApiClient(config);
assert.isDefined(client);
});
});
describe('Variant #2: configuration with all options', () => {
it('should create client with all configuration options', () => {
const config: IProApiConfig = {
apiToken: 'test-token',
timeout: 60000,
version: '5.0.0-beta.20'
};
const client = new ProApiClient(config);
assert.isDefined(client);
});
});
});
describe('obfuscate', () => {
describe('Variant #1: vmObfuscation validation', () => {
it('should throw ApiError when vmObfuscation is not enabled', async () => {
const config: IProApiConfig = {
apiToken: 'test-token'
};
const client = new ProApiClient(config);
try {
await client.obfuscate('const a = 1;', { compact: true });
assert.fail('Should have thrown ApiError');
} catch (error) {
assert.instanceOf(error, ApiError);
assert.include((error as ApiError).message, 'vmObfuscation');
}
});
});
describe('Variant #2: URL without version parameter', () => {
it('should call API without version query parameter when version is not specified', async () => {
const config: IProApiConfig = {
apiToken: 'test-token'
};
const client = new ProApiClient(config);
const mockResponse = new Response(
JSON.stringify({ type: 'result', code: 'obfuscated', sourceMap: '' }),
{ status: 200 }
);
fetchStub.resolves(mockResponse);
await client.obfuscate('const a = 1;', { vmObfuscation: true });
assert.isTrue(fetchStub.calledOnce);
const calledUrl = fetchStub.firstCall.args[0];
assert.strictEqual(calledUrl, API_URL);
});
});
describe('Variant #3: URL with version parameter', () => {
it('should call API with version query parameter when version is specified', async () => {
const config: IProApiConfig = {
apiToken: 'test-token',
version: '5.0.0-beta.20'
};
const client = new ProApiClient(config);
const mockResponse = new Response(
JSON.stringify({ type: 'result', code: 'obfuscated', sourceMap: '' }),
{ status: 200 }
);
fetchStub.resolves(mockResponse);
await client.obfuscate('const a = 1;', { vmObfuscation: true });
assert.isTrue(fetchStub.calledOnce);
const calledUrl = fetchStub.firstCall.args[0];
assert.strictEqual(calledUrl, `${API_URL}?version=5.0.0-beta.20`);
});
});
describe('Variant #4: version parameter encoding', () => {
it('should properly encode version parameter in URL', async () => {
const config: IProApiConfig = {
apiToken: 'test-token',
version: '5.0.0-beta.22'
};
const client = new ProApiClient(config);
const mockResponse = new Response(
JSON.stringify({ type: 'result', code: 'obfuscated', sourceMap: '' }),
{ status: 200 }
);
fetchStub.resolves(mockResponse);
await client.obfuscate('const a = 1;', { vmObfuscation: true });
assert.isTrue(fetchStub.calledOnce);
const calledUrl = fetchStub.firstCall.args[0];
// encodeURIComponent('5.0.0-beta.22') === '5.0.0-beta.22' (no special chars)
assert.strictEqual(calledUrl, `${API_URL}?version=5.0.0-beta.22`);
});
});
describe('Variant #5: authorization header', () => {
it('should include Authorization header with Bearer token', async () => {
const config: IProApiConfig = {
apiToken: 'my-secret-token',
version: '5.0.0-beta.15'
};
const client = new ProApiClient(config);
const mockResponse = new Response(
JSON.stringify({ type: 'result', code: 'obfuscated', sourceMap: '' }),
{ status: 200 }
);
fetchStub.resolves(mockResponse);
await client.obfuscate('const a = 1;', { vmObfuscation: true });
assert.isTrue(fetchStub.calledOnce);
const calledOptions = fetchStub.firstCall.args[1];
assert.strictEqual(calledOptions.headers['Authorization'], 'Bearer my-secret-token');
});
});
describe('Variant #6: successful obfuscation result', () => {
it('should return obfuscation result with code and sourceMap', async () => {
const config: IProApiConfig = {
apiToken: 'test-token',
version: '5.0.0-beta.20'
};
const client = new ProApiClient(config);
const mockResponse = new Response(
JSON.stringify({ type: 'result', code: 'var _0x123=1;', sourceMap: '{"version":3}' }),
{ status: 200 }
);
fetchStub.resolves(mockResponse);
const result = await client.obfuscate('const a = 1;', { vmObfuscation: true });
assert.strictEqual(result.getObfuscatedCode(), 'var _0x123=1;');
assert.strictEqual(result.getSourceMap(), '{"version":3}');
});
});
describe('Variant #7: chunked response', () => {
it('should reassemble chunked response correctly', async () => {
const config: IProApiConfig = {
apiToken: 'test-token',
version: '5.0.0-beta.10'
};
const client = new ProApiClient(config);
const chunks = [
JSON.stringify({ type: 'progress', message: 'Processing...' }),
JSON.stringify({ type: 'chunk', field: 'code', data: 'var _0x', index: 0, total: 2 }),
JSON.stringify({ type: 'chunk', field: 'code', data: '123=1;', index: 1, total: 2 }),
JSON.stringify({ type: 'chunk_end', sourceMap: '' })
].join('\n');
const mockResponse = new Response(chunks, { status: 200 });
fetchStub.resolves(mockResponse);
const result = await client.obfuscate('const a = 1;', { vmObfuscation: true });
assert.strictEqual(result.getObfuscatedCode(), 'var _0x123=1;');
});
});
describe('Variant #8: API error response', () => {
it('should throw ApiError when API returns error message', async () => {
const config: IProApiConfig = {
apiToken: 'invalid-token',
version: '5.0.0-beta.20'
};
const client = new ProApiClient(config);
const mockResponse = new Response(
JSON.stringify({ type: 'error', message: 'Invalid API token' }),
{ status: 401 }
);
fetchStub.resolves(mockResponse);
try {
await client.obfuscate('const a = 1;', { vmObfuscation: true });
assert.fail('Should have thrown ApiError');
} catch (error) {
assert.instanceOf(error, ApiError);
assert.include((error as ApiError).message, 'Invalid API token');
}
});
});
describe('Variant #9: progress callback', () => {
it('should call progress callback for progress messages', async () => {
const config: IProApiConfig = {
apiToken: 'test-token',
version: '5.0.0-beta.20'
};
const client = new ProApiClient(config);
const progressMessages: string[] = [];
const onProgress = (message: string) => {
progressMessages.push(message);
};
const chunks = [
JSON.stringify({ type: 'progress', message: 'Validating...' }),
JSON.stringify({ type: 'progress', message: 'Obfuscating...' }),
JSON.stringify({ type: 'result', code: 'var a=1;', sourceMap: '' })
].join('\n');
const mockResponse = new Response(chunks, { status: 200 });
fetchStub.resolves(mockResponse);
await client.obfuscate('const a = 1;', { vmObfuscation: true }, onProgress);
assert.deepEqual(progressMessages, ['Validating...', 'Obfuscating...']);
});
});
});
});