chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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',
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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/,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user