chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
const uploadToIpfsSplit = vi.fn();
|
||||
const getAuthConfig = vi.fn();
|
||||
const getRootDomain = vi.fn(async () => 'pinme.test');
|
||||
const getUid = vi.fn(() => 'device-uid');
|
||||
|
||||
vi.mock('../../bin/utils/uploadToIpfsSplit', () => ({
|
||||
default: uploadToIpfsSplit,
|
||||
}));
|
||||
|
||||
vi.mock('../../bin/utils/webLogin', () => ({
|
||||
getAuthConfig,
|
||||
}));
|
||||
|
||||
vi.mock('../../bin/utils/pinmeApi', () => ({
|
||||
getRootDomain,
|
||||
}));
|
||||
|
||||
vi.mock('../../bin/utils/getDeviceId', () => ({
|
||||
getUid,
|
||||
}));
|
||||
|
||||
async function loadService(env: Record<string, string | undefined> = {}) {
|
||||
vi.resetModules();
|
||||
process.env.IPFS_PREVIEW_URL =
|
||||
env.IPFS_PREVIEW_URL || 'https://preview.pinme.test/#/preview/';
|
||||
process.env.PROJECT_PREVIEW_URL =
|
||||
env.PROJECT_PREVIEW_URL || 'https://project.pinme.test/';
|
||||
if ('SECRET_KEY' in env) {
|
||||
if (env.SECRET_KEY === undefined) {
|
||||
delete process.env.SECRET_KEY;
|
||||
} else {
|
||||
process.env.SECRET_KEY = env.SECRET_KEY;
|
||||
}
|
||||
} else {
|
||||
delete process.env.SECRET_KEY;
|
||||
}
|
||||
return import('../../bin/services/uploadService');
|
||||
}
|
||||
|
||||
describe('uploadService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.doUnmock('crypto-js');
|
||||
getRootDomain.mockResolvedValue('pinme.test');
|
||||
getUid.mockReturnValue('device-uid');
|
||||
});
|
||||
|
||||
test('prefers DNS URL over PinMe, short, and management URLs', async () => {
|
||||
const { resolveUploadUrls } = await loadService();
|
||||
|
||||
const result = await resolveUploadUrls(
|
||||
'bafybeicid',
|
||||
{
|
||||
dnsUrl: 'example.com/',
|
||||
pinmeUrl: 'my-site',
|
||||
shortUrl: 'short',
|
||||
},
|
||||
undefined,
|
||||
'uid-1',
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
publicUrl: 'https://example.com',
|
||||
managementUrl: 'https://preview.pinme.test/#/preview/bafybeicid',
|
||||
});
|
||||
});
|
||||
|
||||
test('appends root domain for bare PinMe subdomains', async () => {
|
||||
const { resolveUploadUrls } = await loadService();
|
||||
|
||||
await expect(
|
||||
resolveUploadUrls('bafybeicid', { pinmeUrl: 'demo' }, undefined, 'uid-1'),
|
||||
).resolves.toMatchObject({
|
||||
publicUrl: 'https://demo.pinme.test',
|
||||
});
|
||||
});
|
||||
|
||||
test('keeps absolute and dotted short URLs without root-domain lookup', async () => {
|
||||
const { resolveUploadUrls } = await loadService();
|
||||
|
||||
await expect(
|
||||
resolveUploadUrls(
|
||||
'bafybeicid',
|
||||
{ shortUrl: 'https://already.example/path/' },
|
||||
undefined,
|
||||
'uid-1',
|
||||
),
|
||||
).resolves.toMatchObject({
|
||||
publicUrl: 'https://already.example/path/',
|
||||
});
|
||||
|
||||
await expect(
|
||||
resolveUploadUrls(
|
||||
'bafybeicid',
|
||||
{ shortUrl: 'http://already.example/path/' },
|
||||
undefined,
|
||||
'uid-1',
|
||||
),
|
||||
).resolves.toMatchObject({
|
||||
publicUrl: 'http://already.example/path/',
|
||||
});
|
||||
|
||||
await expect(
|
||||
resolveUploadUrls(
|
||||
'bafybeicid',
|
||||
{ shortUrl: 'short.example' },
|
||||
undefined,
|
||||
'uid-1',
|
||||
),
|
||||
).resolves.toMatchObject({
|
||||
publicUrl: 'https://short.example',
|
||||
});
|
||||
|
||||
await expect(
|
||||
resolveUploadUrls(
|
||||
'bafybeicid',
|
||||
{ shortUrl: 'xhttp://short.example/' },
|
||||
undefined,
|
||||
'uid-1',
|
||||
),
|
||||
).resolves.toMatchObject({
|
||||
publicUrl: 'https://xhttp://short.example/',
|
||||
});
|
||||
});
|
||||
|
||||
test('accepts http PinMe URLs and preserves explicit protocol', async () => {
|
||||
const { resolveUploadUrls } = await loadService();
|
||||
|
||||
await expect(
|
||||
resolveUploadUrls(
|
||||
'bafybeicid',
|
||||
{ pinmeUrl: 'http://demo.pinme.test/path/' },
|
||||
undefined,
|
||||
'uid-1',
|
||||
),
|
||||
).resolves.toMatchObject({
|
||||
publicUrl: 'http://demo.pinme.test/path',
|
||||
});
|
||||
});
|
||||
|
||||
test('falls back to protocol-prefixed text for invalid preferred URLs', async () => {
|
||||
const { resolveUploadUrls } = await loadService();
|
||||
|
||||
await expect(
|
||||
resolveUploadUrls(
|
||||
'bafybeicid',
|
||||
{ dnsUrl: 'bad host/' },
|
||||
undefined,
|
||||
'uid-1',
|
||||
),
|
||||
).resolves.toMatchObject({
|
||||
publicUrl: 'https://bad host',
|
||||
});
|
||||
});
|
||||
|
||||
test('does not treat embedded protocol text as an absolute preferred URL', async () => {
|
||||
const { resolveUploadUrls } = await loadService();
|
||||
|
||||
await expect(
|
||||
resolveUploadUrls(
|
||||
'bafybeicid',
|
||||
{ dnsUrl: 'xhttp://example.com/' },
|
||||
undefined,
|
||||
'uid-1',
|
||||
),
|
||||
).resolves.toMatchObject({
|
||||
publicUrl: 'https://xhttp//example.com',
|
||||
});
|
||||
});
|
||||
|
||||
test('falls back to bare subdomain when root domain lookup fails', async () => {
|
||||
const { resolveUploadUrls } = await loadService();
|
||||
getRootDomain.mockRejectedValueOnce(new Error('root domain failed'));
|
||||
|
||||
await expect(
|
||||
resolveUploadUrls('bafybeicid', { pinmeUrl: 'demo' }, undefined, 'uid-1'),
|
||||
).resolves.toMatchObject({
|
||||
publicUrl: 'https://demo',
|
||||
});
|
||||
});
|
||||
|
||||
test('ignores blank preferred URLs and falls back to management URL', async () => {
|
||||
const { resolveUploadUrls } = await loadService();
|
||||
|
||||
await expect(
|
||||
resolveUploadUrls(
|
||||
'bafybeicid',
|
||||
{ dnsUrl: ' ', pinmeUrl: '', shortUrl: ' ' },
|
||||
undefined,
|
||||
'uid-1',
|
||||
),
|
||||
).resolves.toEqual({
|
||||
publicUrl: 'https://preview.pinme.test/#/preview/bafybeicid',
|
||||
managementUrl: 'https://preview.pinme.test/#/preview/bafybeicid',
|
||||
});
|
||||
});
|
||||
|
||||
test('falls back to project management URL when project name is present', async () => {
|
||||
const { resolveUploadUrls } = await loadService();
|
||||
|
||||
await expect(
|
||||
resolveUploadUrls('bafybeicid', undefined, 'demo-project', 'uid-1'),
|
||||
).resolves.toEqual({
|
||||
publicUrl: 'https://project.pinme.test/demo-project',
|
||||
managementUrl: 'https://project.pinme.test/demo-project',
|
||||
});
|
||||
});
|
||||
|
||||
test('trims project names and falls back to device uid when uid is blank', async () => {
|
||||
const { resolveUploadUrls } = await loadService();
|
||||
|
||||
await expect(
|
||||
resolveUploadUrls('bafybeicid', undefined, ' demo-project ', ' '),
|
||||
).resolves.toEqual({
|
||||
publicUrl: 'https://project.pinme.test/demo-project',
|
||||
managementUrl: 'https://project.pinme.test/demo-project',
|
||||
});
|
||||
expect(getUid).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('uses secretKey to hide raw CID in preview management URLs', async () => {
|
||||
const { resolveUploadUrls } = await loadService({ SECRET_KEY: 'secret' });
|
||||
|
||||
const result = await resolveUploadUrls(
|
||||
'bafybeicid',
|
||||
undefined,
|
||||
undefined,
|
||||
'uid-1',
|
||||
);
|
||||
|
||||
expect(result.managementUrl).toMatch(
|
||||
/^https:\/\/preview\.pinme\.test\/#\/preview\/.+/,
|
||||
);
|
||||
expect(result.managementUrl).not.toContain('bafybeicid');
|
||||
expect(result.publicUrl).toBe(result.managementUrl);
|
||||
});
|
||||
|
||||
test('secretKey encryption produces URL-safe CID tokens that include uid input', async () => {
|
||||
const { resolveUploadUrls } = await loadService({ SECRET_KEY: 'secret' });
|
||||
|
||||
const first = await resolveUploadUrls(
|
||||
'bafybeicid',
|
||||
undefined,
|
||||
undefined,
|
||||
'uid-1',
|
||||
);
|
||||
const second = await resolveUploadUrls(
|
||||
'bafybeicid',
|
||||
undefined,
|
||||
undefined,
|
||||
'uid-2',
|
||||
);
|
||||
const firstToken = first.managementUrl.split('/preview/').at(-1)!;
|
||||
const secondToken = second.managementUrl.split('/preview/').at(-1)!;
|
||||
|
||||
expect(firstToken).not.toBe(secondToken);
|
||||
expect(firstToken).not.toContain('bafybeicid');
|
||||
expect(firstToken).not.toMatch(/[+/=]/);
|
||||
expect(secondToken).not.toMatch(/[+/=]/);
|
||||
});
|
||||
|
||||
test('secretKey encryption sanitizes plus slash and padding deterministically', async () => {
|
||||
const encrypt = vi.fn((message: string) => ({
|
||||
toString: () => `+/${message}==`,
|
||||
}));
|
||||
vi.doMock('crypto-js', () => ({
|
||||
default: {
|
||||
RC4: { encrypt },
|
||||
},
|
||||
}));
|
||||
const { resolveUploadUrls } = await loadService({ SECRET_KEY: 'secret' });
|
||||
|
||||
const result = await resolveUploadUrls(
|
||||
'bafybeicid',
|
||||
undefined,
|
||||
undefined,
|
||||
'uid-1',
|
||||
);
|
||||
|
||||
expect(encrypt).toHaveBeenCalledWith('bafybeicid-uid-1', 'secret');
|
||||
expect(result.managementUrl).toBe(
|
||||
'https://preview.pinme.test/#/preview/-_bafybeicid-uid-1',
|
||||
);
|
||||
expect(result.managementUrl.split('/preview/').at(-1)).not.toMatch(
|
||||
/[+/=]/,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
test('uploadPath rejects when auth config is absent', async () => {
|
||||
const { uploadPath } = await loadService();
|
||||
getAuthConfig.mockReturnValue(null);
|
||||
|
||||
await expect(uploadPath('/tmp/site')).rejects.toThrow(/Please login first/);
|
||||
expect(uploadToIpfsSplit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('uploadPath returns normalized upload result URLs', async () => {
|
||||
const { uploadPath } = await loadService();
|
||||
getAuthConfig.mockReturnValue({
|
||||
address: '0xabc',
|
||||
token: 'token',
|
||||
});
|
||||
uploadToIpfsSplit.mockResolvedValue({
|
||||
contentHash: 'bafybeicid',
|
||||
shortUrl: 'short',
|
||||
});
|
||||
|
||||
await expect(uploadPath('/tmp/site', { action: 'upload' })).resolves.toEqual({
|
||||
contentHash: 'bafybeicid',
|
||||
shortUrl: 'short',
|
||||
pinmeUrl: undefined,
|
||||
dnsUrl: undefined,
|
||||
publicUrl: 'https://short.pinme.test',
|
||||
managementUrl: 'https://preview.pinme.test/#/preview/bafybeicid',
|
||||
});
|
||||
expect(uploadToIpfsSplit).toHaveBeenCalledWith('/tmp/site', {
|
||||
action: 'upload',
|
||||
importAsCar: undefined,
|
||||
projectName: undefined,
|
||||
uid: '0xabc',
|
||||
});
|
||||
});
|
||||
|
||||
test('uploadPath rejects upload responses without a content hash', async () => {
|
||||
const { uploadPath } = await loadService();
|
||||
getAuthConfig.mockReturnValue({
|
||||
address: '0xabc',
|
||||
token: 'token',
|
||||
});
|
||||
uploadToIpfsSplit.mockResolvedValue({});
|
||||
|
||||
await expect(uploadPath('/tmp/site')).rejects.toThrow(/no content hash/);
|
||||
|
||||
uploadToIpfsSplit.mockResolvedValueOnce(null);
|
||||
await expect(uploadPath('/tmp/site')).rejects.toThrow(/no content hash/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user