chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:30:28 +08:00
commit ba73d7ef36
160 changed files with 37714 additions and 0 deletions
+233
View File
@@ -0,0 +1,233 @@
import { beforeEach, describe, expect, test, vi } from 'vitest';
import nock from 'nock';
async function loadApiClient(env: Record<string, string> = {}) {
vi.resetModules();
vi.doUnmock('../../bin/utils/webLogin');
process.env.PINME_API_BASE = env.PINME_API_BASE || 'https://api.pinme.test';
return import('../../bin/utils/apiClient');
}
async function loadApiClientWithAuthMock() {
vi.resetModules();
process.env.PINME_API_BASE = 'https://api.pinme.test';
vi.doMock('../../bin/utils/webLogin', () => ({
getAuthHeaders: () => ({
'token-address': '0xabc',
'authentication-tokens': 'secret-token',
}),
}));
return import('../../bin/utils/apiClient');
}
async function loadApiClientWithThrowingAuthMock() {
vi.resetModules();
process.env.PINME_API_BASE = 'https://api.pinme.test';
vi.doMock('../../bin/utils/webLogin', () => ({
getAuthHeaders: () => {
throw new Error('auth config unreadable');
},
}));
return import('../../bin/utils/apiClient');
}
describe('apiClient', () => {
beforeEach(() => {
nock.cleanAll();
});
test('returns successful business responses', async () => {
const { createPinmeApiClient } = await loadApiClient();
nock('https://api.pinme.test')
.get('/root_domain')
.reply(200, { code: 200, data: { domain: 'pinme.test' } });
const response = await createPinmeApiClient().get('/root_domain');
expect(response.data.data.domain).toBe('pinme.test');
expect(nock.isDone()).toBe(true);
});
test('turns non-200 business codes into CliError', async () => {
const { createPinmeApiClient } = await loadApiClient();
nock('https://api.pinme.test')
.post('/bind_pinme_domain')
.reply(200, { code: 500, msg: 'Domain is taken' });
await expect(
createPinmeApiClient().post('/bind_pinme_domain', {
domain_name: 'demo',
hash: 'bafy',
}),
).rejects.toMatchObject({
name: 'CliError',
stage: 'API request',
message: 'Domain is taken',
details: ['Request: POST /bind_pinme_domain', 'Business code: 500'],
});
});
test('wraps HTTP failures with request context', async () => {
const { createPinmeApiClient } = await loadApiClient();
nock('https://api.pinme.test')
.get('/my_domains')
.reply(503, { message: 'Temporarily unavailable' });
await expect(createPinmeApiClient().get('/my_domains')).rejects.toMatchObject(
{
name: 'CliError',
stage: 'API request',
message: 'Temporarily unavailable',
details: ['Request: GET /my_domains', 'HTTP status: 503'],
},
);
});
test('injects auth headers from local auth config by default', async () => {
const { createPinmeApiClient } = await loadApiClientWithAuthMock();
let capturedHeaders: Record<string, string | string[] | undefined> = {};
nock('https://api.pinme.test')
.get('/my_domains')
.reply(function () {
capturedHeaders = this.req.headers;
return [200, { code: 200, data: [] }];
});
await expect(createPinmeApiClient().get('/my_domains')).resolves.toMatchObject(
{
data: { code: 200, data: [] },
},
);
expect(capturedHeaders['token-address']).toBe('0xabc');
expect(capturedHeaders['authentication-tokens']).toBe('secret-token');
});
test('omits auth headers when includeAuth is false', async () => {
const { createPinmeApiClient } = await loadApiClientWithAuthMock();
nock('https://api.pinme.test', {
badheaders: ['token-address', 'authentication-tokens'],
})
.get('/public')
.reply(200, { ok: true });
const response = await createPinmeApiClient({ includeAuth: false }).get(
'/public',
);
expect(response.data).toEqual({ ok: true });
});
test('merges custom headers over defaults', async () => {
const { createPinmeApiClient } = await loadApiClient();
nock('https://api.pinme.test', {
reqheaders: {
'user-agent': 'Custom-Agent',
'x-test-suite': 'api-client',
},
})
.get('/headers')
.reply(200, { ok: true });
await createPinmeApiClient({
includeAuth: false,
headers: {
'User-Agent': 'Custom-Agent',
'X-Test-Suite': 'api-client',
},
}).get('/headers');
expect(nock.isDone()).toBe(true);
});
test('sends default JSON CLI headers', async () => {
const { createPinmeApiClient } = await loadApiClient();
let capturedHeaders: Record<string, string | string[] | undefined> = {};
nock('https://api.pinme.test')
.get('/headers')
.reply(function () {
capturedHeaders = this.req.headers;
return [200, { ok: true }];
});
await createPinmeApiClient({ includeAuth: false }).get('/headers');
expect(capturedHeaders.accept).toBe('*/*');
expect(capturedHeaders['content-type']).toBe('application/json');
expect(capturedHeaders['user-agent']).toBe('Pinme-CLI');
expect(capturedHeaders.connection).toBe('keep-alive');
});
test('continues without auth headers when local auth config is unreadable', async () => {
const { createPinmeApiClient } = await loadApiClientWithThrowingAuthMock();
nock('https://api.pinme.test', {
badheaders: ['token-address', 'authentication-tokens'],
})
.get('/public')
.reply(200, { ok: true });
await expect(createPinmeApiClient().get('/public')).resolves.toMatchObject({
data: { ok: true },
});
});
test('does not treat non-object response bodies as business errors', async () => {
const { createPinmeApiClient } = await loadApiClient();
nock('https://api.pinme.test').get('/plain').reply(200, 'code 500');
const response = await createPinmeApiClient({ includeAuth: false }).get(
'/plain',
);
expect(response.data).toBe('code 500');
});
test('omits request context when axios config has no URL', async () => {
const { createApiClient } = await loadApiClient();
const client = createApiClient({
includeAuth: false,
baseURL: 'https://api.pinme.test',
});
await expect(
client.request({
adapter: async (config) => ({
data: { code: 500, msg: 'Adapter business failure' },
status: 200,
statusText: 'OK',
headers: {},
config,
}),
}),
).rejects.toMatchObject({
message: 'Adapter business failure',
details: ['Business code: 500'],
});
});
test('uses GET as the default request descriptor method', async () => {
const { createApiClient } = await loadApiClient();
const client = createApiClient({
includeAuth: false,
baseURL: 'https://api.pinme.test',
});
await expect(
client.request({
url: '/adapter-default-method',
adapter: async (config) => ({
data: { code: 500, msg: 'Adapter business failure' },
status: 200,
statusText: 'OK',
headers: {},
config,
}),
}),
).rejects.toMatchObject({
message: 'Adapter business failure',
details: [
'Request: GET /adapter-default-method',
'Business code: 500',
],
});
});
});
+491
View File
@@ -0,0 +1,491 @@
import { beforeEach, describe, expect, test, vi } from 'vitest';
import nock from 'nock';
async function loadPinmeApi(env: Record<string, string>) {
vi.resetModules();
Object.assign(process.env, env);
return import('../../bin/utils/pinmeApi');
}
describe('pinmeApi', () => {
beforeEach(() => {
nock.cleanAll();
});
test('returns DNS domains as available without an API call', async () => {
const { checkDomainAvailable } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
});
await expect(checkDomainAvailable('example.com')).resolves.toEqual({
is_valid: true,
});
expect(nock.isDone()).toBe(true);
});
test('checks PinMe subdomain availability through configured endpoint', async () => {
const { checkDomainAvailable } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
PINME_CHECK_DOMAIN_PATH: '/check_domain',
});
nock('https://api.pinme.test')
.post('/check_domain', { domain_name: 'demo' })
.reply(200, { data: { is_valid: false, error: 'taken' } });
await expect(checkDomainAvailable('demo')).resolves.toEqual({
is_valid: false,
error: 'taken',
});
});
test('defaults subdomain availability to true for unexpected successful shapes', async () => {
const { checkDomainAvailable } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
PINME_CHECK_DOMAIN_PATH: '/check_domain',
});
nock('https://api.pinme.test')
.post('/check_domain', { domain_name: 'demo' })
.reply(200, { code: 200, data: { unexpected: true } });
await expect(checkDomainAvailable('demo')).resolves.toEqual({
is_valid: true,
});
});
test('checks top-level domain availability and surfaces recoverable HTTP failures', async () => {
const { checkDomainAvailable } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
PINME_CHECK_DOMAIN_PATH: '/check_domain',
});
nock('https://api.pinme.test')
.post('/check_domain', { domain_name: 'direct' })
.reply(200, { is_valid: false, error: 'reserved' })
.post('/check_domain', { domain_name: 'missing' })
.reply(404, { message: 'not found' });
await expect(checkDomainAvailable('direct')).resolves.toEqual({
is_valid: false,
error: 'reserved',
});
await expect(checkDomainAvailable('missing')).rejects.toThrow('not found');
});
test('caches root domain until force refresh', async () => {
const { getRootDomain } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
});
nock('https://api.pinme.test')
.get('/root_domain')
.reply(200, { code: 200, data: { domain: 'first.pinme.test' } })
.get('/root_domain')
.reply(200, { code: 200, data: { domain: 'second.pinme.test' } });
await expect(getRootDomain()).resolves.toBe('first.pinme.test');
await expect(getRootDomain()).resolves.toBe('first.pinme.test');
await expect(getRootDomain(true)).resolves.toBe('second.pinme.test');
});
test('getRootDomain rejects successful responses without a domain', async () => {
const { getRootDomain } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
});
nock('https://api.pinme.test')
.get('/root_domain')
.reply(200, { code: 200, msg: 'domain missing', data: {} });
await expect(getRootDomain(true)).rejects.toThrow('domain missing');
});
test('binds anonymous devices and returns false on token expiration', async () => {
const { bindAnonymousDevice } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
});
nock('https://api.pinme.test')
.post('/bind_anonymous', { anonymous_uid: 'anon-1' })
.reply(200, { code: 200 })
.post('/bind_anonymous', { anonymous_uid: 'anon-2' })
.reply(401, { message: 'token expired' });
await expect(bindAnonymousDevice('anon-1')).resolves.toBe(true);
await expect(bindAnonymousDevice('anon-2')).resolves.toBe(false);
});
test('bindAnonymousDevice returns false for non-token failures', async () => {
const { bindAnonymousDevice } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
});
nock('https://api.pinme.test')
.post('/bind_anonymous', { anonymous_uid: 'anon-fail' })
.reply(500, { message: 'server exploded' });
await expect(bindAnonymousDevice('anon-fail')).resolves.toBe(false);
});
test('throws token expired for auth failures', async () => {
const { getMyDomains } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
});
nock('https://api.pinme.test')
.get('/my_domains')
.reply(401, { message: 'token expired' });
await expect(getMyDomains()).rejects.toThrow('Token expired');
});
test('detects token expiration from business codes and localized messages', async () => {
const { checkDomainAvailable, getMyDomains } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
PINME_CHECK_DOMAIN_PATH: '/check_domain',
});
nock('https://api.pinme.test')
.post('/check_domain', { domain_name: 'demo' })
.reply(200, { code: 10001, msg: '登录已过期' })
.get('/my_domains')
.reply(200, { code: 'TOKEN_EXPIRED', msg: 'token expired' });
await expect(checkDomainAvailable('demo')).rejects.toThrow(
'Token expired',
);
await expect(getMyDomains()).rejects.toThrow('Token expired');
});
test('reads domain list variants and returns empty arrays for business failures', async () => {
const { getMyDomains } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
});
nock('https://api.pinme.test')
.get('/my_domains')
.reply(200, {
code: 200,
data: {
list: [
{
domain_name: 'demo',
domain_type: 1,
bind_time: 1,
expire_time: 2,
},
],
},
})
.get('/my_domains')
.reply(200, { msg: 'missing code' });
await expect(getMyDomains()).resolves.toHaveLength(1);
await expect(getMyDomains()).resolves.toEqual([]);
});
test('reads array domain lists and treats business auth codes as expired', async () => {
const { getMyDomains } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
});
nock('https://api.pinme.test')
.get('/my_domains')
.reply(200, {
code: 200,
data: [
{
domain_name: 'array-demo',
domain_type: 1,
bind_time: 1,
expire_time: 2,
},
],
})
.get('/my_domains')
.reply(200, { code: 403, msg: 'auth failed' });
await expect(getMyDomains()).resolves.toEqual([
expect.objectContaining({ domain_name: 'array-demo' }),
]);
await expect(getMyDomains()).rejects.toThrow('Token expired');
});
test('getMyDomains handles unsupported payloads and both auth business codes', async () => {
const { getMyDomains } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
});
nock('https://api.pinme.test')
.get('/my_domains')
.reply(200, { code: 200, data: { list: 'not an array' } })
.get('/my_domains')
.reply(200, { code: 401, msg: 'auth failed' });
await expect(getMyDomains()).resolves.toEqual([]);
await expect(getMyDomains()).rejects.toThrow('Token expired');
});
test('binds DNS domains with auth headers', async () => {
const { bindDnsDomainV4 } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
});
nock('https://api.pinme.test', {
reqheaders: {
'x-auth-token': 'token',
'x-token-address': '0xabc',
},
})
.post('/bind_dns', { domain_name: 'example.com', hash: 'bafy' })
.reply(200, {
code: 200,
msg: 'ok',
data: { domain_name: 'example.com', hash: 'bafy' },
});
await expect(
bindDnsDomainV4('example.com', 'bafy', '0xabc', 'token'),
).resolves.toMatchObject({
code: 200,
data: { domain_name: 'example.com' },
});
});
test('binds PinMe subdomains and reports wallet balance and VIP status', async () => {
const { bindPinmeDomain, getWalletBalance, isVip } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
});
nock('https://api.pinme.test')
.post('/bind_pinme_domain', {
domain_name: 'demo',
hash: 'bafy',
project_name: 'project',
})
.reply(200, { code: 200 })
.get('/pay/wallet/balance')
.reply(200, {
code: 200,
msg: 'ok',
data: { wallet_balance_usd: 12.5 },
})
.get('/is_vip')
.reply(200, {
code: 200,
msg: 'ok',
data: { is_vip: true },
});
await expect(bindPinmeDomain('demo', 'bafy', 'project')).resolves.toBe(true);
await expect(getWalletBalance('0xabc', 'token')).resolves.toMatchObject({
data: { wallet_balance_usd: 12.5 },
});
await expect(isVip('0xabc', 'token')).resolves.toMatchObject({
data: { is_vip: true },
});
});
test('bindPinmeDomain returns false for successful responses without code 200', async () => {
const { bindPinmeDomain } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
});
nock('https://api.pinme.test')
.post('/bind_pinme_domain', {
domain_name: 'demo',
hash: 'bafy',
})
.reply(200, { msg: 'missing code' })
.post('/bind_pinme_domain', {
domain_name: 'project-demo',
hash: 'bafy-project',
project_name: 'project',
})
.reply(200, { code: 200, msg: 'ok' });
await expect(bindPinmeDomain('demo', 'bafy')).resolves.toBe(false);
await expect(
bindPinmeDomain('project-demo', 'bafy-project', 'project'),
).resolves.toBe(true);
});
test('sends account auth headers for wallet and VIP requests', async () => {
const { getWalletBalance, isVip } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
});
nock('https://api.pinme.test', {
reqheaders: {
'authentication-tokens': 'wallet-token',
'token-address': '0xwallet',
},
})
.get('/pay/wallet/balance')
.reply(200, { code: 200, msg: 'ok', data: { wallet_balance_usd: 1 } });
nock('https://api.pinme.test', {
reqheaders: {
'x-auth-token': 'vip-token',
'x-token-address': '0xvip',
},
})
.get('/is_vip')
.reply(200, { code: 200, msg: 'ok', data: { is_vip: false } });
await expect(getWalletBalance('0xwallet', 'wallet-token')).resolves.toEqual(
{
code: 200,
msg: 'ok',
data: { wallet_balance_usd: 1 },
},
);
await expect(isVip('0xvip', 'vip-token')).resolves.toEqual({
code: 200,
msg: 'ok',
data: { is_vip: false },
});
});
test('surfaces token expiration from bind and account APIs', async () => {
const { bindPinmeDomain, bindDnsDomainV4, getWalletBalance, isVip } =
await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
});
nock('https://api.pinme.test')
.post('/bind_pinme_domain')
.reply(403, { message: 'invalid token' })
.post('/bind_dns')
.reply(401, { message: 'token expired' })
.get('/pay/wallet/balance')
.reply(401, { message: 'unauthorized' })
.get('/is_vip')
.reply(401, { message: 'auth failed' });
await expect(bindPinmeDomain('demo', 'bafy')).rejects.toThrow(
'Token expired',
);
await expect(
bindDnsDomainV4('example.com', 'bafy', '0xabc', 'token'),
).rejects.toThrow('Token expired');
await expect(getWalletBalance('0xabc', 'token')).rejects.toThrow(
'Token expired',
);
await expect(isVip('0xabc', 'token')).rejects.toThrow('Token expired');
});
test('requests CAR export through the CAR API client', async () => {
const { requestCarExport } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
CAR_API_BASE: 'https://car.pinme.test',
});
nock('https://car.pinme.test')
.post('/car/export')
.query({ cid: 'bafy', uid: 'uid-1' })
.reply(200, {
code: 200,
msg: 'ok',
data: {
cid: 'bafy',
status: 'processing',
task_id: 'task-1',
},
});
await expect(requestCarExport('bafy', 'uid-1')).resolves.toEqual({
cid: 'bafy',
status: 'processing',
task_id: 'task-1',
});
});
test('checks CAR export status and normalizes API failures', async () => {
const { checkCarExportStatus, requestCarExport } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
CAR_API_BASE: 'https://car.pinme.test',
});
nock('https://car.pinme.test')
.get('/car/export/status')
.query({ task_id: 'task-1' })
.reply(200, {
code: 200,
msg: 'ok',
data: {
task_id: 'task-1',
cid: 'bafy',
status: 'completed',
download_url: 'https://download.pinme.test/file.car',
},
})
.post('/car/export')
.query({ cid: 'bad', uid: 'uid-1' })
.reply(200, { code: 500, msg: 'Export failed' });
await expect(checkCarExportStatus('task-1')).resolves.toMatchObject({
status: 'completed',
download_url: 'https://download.pinme.test/file.car',
});
await expect(requestCarExport('bad', 'uid-1')).rejects.toThrow(
/Failed to request CAR export: Export failed|Export failed/,
);
});
test('rejects CAR success codes that omit payload data', async () => {
const { checkCarExportStatus, requestCarExport } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
CAR_API_BASE: 'https://car.pinme.test',
});
nock('https://car.pinme.test')
.post('/car/export')
.query({ cid: 'empty', uid: 'uid-1' })
.reply(200, { code: 200, msg: 'missing export data' })
.get('/car/export/status')
.query({ task_id: 'empty-task' })
.reply(200, { code: 200, msg: 'missing status data' });
await expect(requestCarExport('empty', 'uid-1')).rejects.toThrow(
/missing export data/,
);
await expect(checkCarExportStatus('empty-task')).rejects.toThrow(
/missing status data/,
);
});
test('normalizes CAR status token expiration and response messages', async () => {
const { checkCarExportStatus } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
CAR_API_BASE: 'https://car.pinme.test',
});
nock('https://car.pinme.test')
.get('/car/export/status')
.query({ task_id: 'expired' })
.reply(401, { message: 'token expired' })
.get('/car/export/status')
.query({ task_id: 'failed' })
.reply(200, { code: 500, msg: 'Task failed' });
await expect(checkCarExportStatus('expired')).rejects.toThrow(
'Token expired',
);
await expect(checkCarExportStatus('failed')).rejects.toThrow(
/Failed to check export status: Task failed|Task failed/,
);
});
test('normalizes CAR HTTP response messages and generic failures', async () => {
const { checkCarExportStatus, requestCarExport } = await loadPinmeApi({
PINME_API_BASE: 'https://api.pinme.test',
CAR_API_BASE: 'https://car.pinme.test',
});
nock('https://car.pinme.test')
.post('/car/export')
.query({ cid: 'http-fail', uid: 'uid-1' })
.reply(503, { msg: 'CAR export unavailable' })
.post('/car/export')
.query({ cid: 'network-fail', uid: 'uid-1' })
.replyWithError('socket closed')
.get('/car/export/status')
.query({ task_id: 'http-fail' })
.reply(500, { msg: 'status unavailable' })
.get('/car/export/status')
.query({ task_id: 'network-fail' })
.replyWithError('status socket closed');
await expect(requestCarExport('http-fail', 'uid-1')).rejects.toThrow(
'CAR export unavailable',
);
await expect(requestCarExport('network-fail', 'uid-1')).rejects.toThrow(
/Failed to request CAR export: socket closed/,
);
await expect(checkCarExportStatus('http-fail')).rejects.toThrow(
'status unavailable',
);
await expect(checkCarExportStatus('network-fail')).rejects.toThrow(
/Failed to check export status: status socket closed/,
);
});
});