@bsv/sdk
Version:
BSV Blockchain Software Development Kit
171 lines • 8.21 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const StorageDownloader_js_1 = require("../StorageDownloader.js");
const index_js_1 = require("../index.js");
const index_js_2 = require("../../overlay-tools/index.js");
const Transaction_js_1 = __importDefault(require("../../transaction/Transaction.js"));
const PushDrop_js_1 = __importDefault(require("../../script/templates/PushDrop.js"));
const index_js_3 = require("../../primitives/index.js");
beforeEach(() => {
jest.restoreAllMocks();
});
describe('StorageDownloader', () => {
let downloader;
beforeEach(() => {
// Create a fresh instance
downloader = new StorageDownloader_js_1.StorageDownloader();
});
describe('resolve()', () => {
it('throws if the lookup response is not "output-list"', async () => {
// Mock the LookupResolver to return something invalid
jest.spyOn(index_js_2.LookupResolver.prototype, 'query').mockResolvedValue({
type: 'something-else',
outputs: []
});
await expect(downloader.resolve('fakeUhrpUrl'))
.rejects
.toThrow('Lookup answer must be an output list');
});
it('decodes each output with Transaction.fromBEEF and PushDrop.decode', async () => {
// 1) Mock lookup response
jest.spyOn(index_js_2.LookupResolver.prototype, 'query').mockResolvedValue({
type: 'output-list',
outputs: [
{ beef: 'fake-beef-a', outputIndex: 0 },
{ beef: 'fake-beef-b', outputIndex: 1 }
]
});
// 2) Mock Transaction.fromBEEF -> returns a dummy transaction
jest.spyOn(Transaction_js_1.default, 'fromBEEF').mockImplementation(() => {
// Each transaction might have multiple outputs; we only care about `outputIndex`
return {
outputs: [
{ lockingScript: {} },
{ lockingScript: {} } // index 1
]
};
});
// 3) Mock PushDrop.decode -> returns { fields: number[][] }
jest.spyOn(PushDrop_js_1.default, 'decode').mockImplementation(() => {
// The decode function returns an object with `fields`,
return {
lockingPublicKey: {},
fields: [
[11],
[22],
[104, 116, 116, 112, 58, 47, 47, 97, 46, 99, 111, 109]
]
};
});
// 4) Mock Utils.toUTF8 to convert that number[] to a string
jest.spyOn(index_js_3.Utils, 'toUTF8').mockReturnValue('http://a.com');
const resolved = await downloader.resolve('fakeUhrpUrl');
expect(resolved).toEqual(['http://a.com', 'http://a.com']);
});
});
describe('download()', () => {
it('throws if UHRP URL is invalid', async () => {
jest.spyOn(index_js_1.StorageUtils, 'isValidURL').mockReturnValue(false);
await expect(downloader.download('invalidUrl'))
.rejects
.toThrow('Invalid parameter UHRP url');
});
it('throws if no hosts are found', async () => {
// Valid UHRP URL
jest.spyOn(index_js_1.StorageUtils, 'isValidURL').mockReturnValue(true);
// Return some random 32-byte hash so we can pass the check
jest.spyOn(index_js_1.StorageUtils, 'getHashFromURL').mockReturnValue(new Array(32).fill(0));
// Force resolve() to return an empty array
jest.spyOn(downloader, 'resolve').mockResolvedValue([]);
await expect(downloader.download('validButUnhostedUrl'))
.rejects
.toThrow('No one currently hosts this file!');
});
it('downloads successfully from the first working host', async () => {
jest.spyOn(index_js_1.StorageUtils, 'isValidURL').mockReturnValue(true);
const knownHash = [
102, 104, 122, 173, 248, 98, 189, 119, 108, 143,
193, 139, 142, 159, 142, 32, 8, 151, 20, 133,
110, 226, 51, 179, 144, 42, 89, 29, 13, 95,
41, 37
];
jest.spyOn(index_js_1.StorageUtils, 'getHashFromURL').mockReturnValue(knownHash);
// Suppose two possible download URLs
jest.spyOn(downloader, 'resolve').mockResolvedValue([
'http://host1/404',
'http://host2/ok'
]);
// The first fetch -> 404, second fetch -> success
const fetchSpy = jest.spyOn(global, 'fetch')
.mockResolvedValueOnce(new Response(null, { status: 404 }))
.mockResolvedValueOnce(new Response(new Uint8Array(32).fill(0), {
status: 200,
headers: { 'Content-Type': 'application/test' }
}));
const result = await downloader.download('validUrl');
expect(fetchSpy).toHaveBeenCalledTimes(2);
expect(result).toEqual({
data: new Array(32).fill(0),
mimeType: 'application/test'
});
});
it('throws if content hash mismatches the UHRP hash', async () => {
jest.spyOn(index_js_1.StorageUtils, 'isValidURL').mockReturnValue(true);
// The expected hash is all zeros
jest.spyOn(index_js_1.StorageUtils, 'getHashFromURL').mockReturnValue(new Array(32).fill(0));
// One potential host
jest.spyOn(downloader, 'resolve').mockResolvedValue([
'http://bad-content.test'
]);
// The fetch returns 32 bytes of all 1's => hash mismatch
jest.spyOn(global, 'fetch').mockResolvedValue(new Response(new Uint8Array(32).fill(1), { status: 200 }));
await expect(downloader.download('validButBadHashUrl'))
.rejects
.toThrow();
});
it('throws if all hosts fail or mismatch', async () => {
jest.spyOn(index_js_1.StorageUtils, 'isValidURL').mockReturnValue(true);
jest.spyOn(index_js_1.StorageUtils, 'getHashFromURL').mockReturnValue(new Array(32).fill(0));
jest.spyOn(downloader, 'resolve').mockResolvedValue([
'http://host1.test',
'http://host2.test'
]);
// Both fetches fail with 500 or something >=400
jest.spyOn(global, 'fetch').mockResolvedValue(new Response(null, { status: 500 }));
await expect(downloader.download('validButNoGoodHostUrl'))
.rejects
.toThrow('Unable to download content from validButNoGoodHostUrl');
});
it('throws if all entries are expired', async () => {
const currentTime = Math.floor(Date.now());
jest.spyOn(index_js_2.LookupResolver.prototype, 'query').mockResolvedValue({
type: 'output-list',
outputs: [
{ beef: 'fake-beef-a', outputIndex: 0 },
{ beef: 'fake-beef-b', outputIndex: 1 }
]
});
jest.spyOn(Transaction_js_1.default, 'fromBEEF').mockImplementation(() => {
return {
outputs: [
{ lockingScript: {} },
{ lockingScript: {} }
]
};
});
jest.spyOn(PushDrop_js_1.default, 'decode').mockImplementation(() => {
return {
lockingPublicKey: {},
fields: [[], [], [], [currentTime - 100]]
};
});
await expect(downloader.resolve('expiredUhrpUrl'))
.resolves
.toEqual(["", ""]);
});
});
});
//# sourceMappingURL=StorageDownloader.test.js.map