diff --git a/package.json b/package.json index 0344328d..b3c981ad 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,6 @@ "engines": { "node": ">=12.0.0", "npm": ">=6.0.0", - "bee": "0.5.3-acbd0e2" + "bee": "0.6.0-acbd0e2" } } diff --git a/src/bee.ts b/src/bee.ts index d82eb833..3a541c9a 100644 --- a/src/bee.ts +++ b/src/bee.ts @@ -1,6 +1,5 @@ import type { Readable } from 'stream' -import * as file from './modules/file' -import * as collection from './modules/collection' +import * as bzz from './modules/bzz' import * as tag from './modules/tag' import * as pinning from './modules/pinning' import * as bytes from './modules/bytes' @@ -45,6 +44,7 @@ import { EthAddress, makeEthAddress, makeHexEthAddress } from './utils/eth' import { wrapBytesWithHelpers } from './utils/bytes' import { assertReference } from './utils/type' import { setJsonData, getJsonData } from './feed/json' +import { makeCollectionFromFS, makeCollectionFromFileList } from './utils/collection' /** * The Bee class provides a way of interacting with the Bee APIs based on the provided url @@ -126,9 +126,9 @@ export class Bee { const contentType = data.type const fileOptions = options !== undefined ? { contentType, ...options } : { contentType } - return file.upload(this.url, fileData, fileName, fileOptions) + return bzz.uploadFile(this.url, fileData, fileName, fileOptions) } else { - return file.upload(this.url, data, name, options) + return bzz.uploadFile(this.url, data, name, options) } } @@ -136,22 +136,24 @@ export class Bee { * Download single file as a byte array * * @param reference Bee file reference + * @param path If reference points to manifest, then this parameter defines path to the file */ - downloadFile(reference: Reference | string): Promise> { + downloadFile(reference: Reference | string, path = ''): Promise> { assertReference(reference) - return file.download(this.url, reference) + return bzz.downloadFile(this.url, reference, path) } /** * Download single file as a readable stream * * @param reference Bee file reference + * @param path If reference points to manifest, then this parameter defines path to the file */ - downloadReadableFile(reference: Reference | string): Promise> { + downloadReadableFile(reference: Reference | string, path = ''): Promise> { assertReference(reference) - return file.downloadReadable(this.url, reference) + return bzz.downloadFileReadable(this.url, reference, path) } /** @@ -165,9 +167,9 @@ export class Bee { * @returns reference of the collection of files */ async uploadFiles(fileList: FileList | File[], options?: CollectionUploadOptions): Promise { - const data = await collection.buildFileListCollection(fileList) + const data = await makeCollectionFromFileList(fileList) - return collection.upload(this.url, data, options) + return bzz.uploadCollection(this.url, data, options) } /** @@ -176,48 +178,14 @@ export class Bee { * Uses the `fs` module of Node.js * * @param dir the path of the files to be uploaded - * @param recursive specifies if the directory should be recursively uploaded * @param options Additional options like tag, encryption, pinning * * @returns reference of the collection of files */ - async uploadFilesFromDirectory(dir: string, recursive = true, options?: CollectionUploadOptions): Promise { - const data = await collection.buildCollection(dir, recursive) + async uploadFilesFromDirectory(dir: string, options?: CollectionUploadOptions): Promise { + const data = await makeCollectionFromFS(dir) - return collection.upload(this.url, data, options) - } - - /** - * Download single file as a byte array from collection given using the path - * - * @param reference Bee collection reference - * @param path Path of the requested file in the collection - * - * @returns file in byte array with metadata - */ - downloadFileFromCollection(reference: Reference | string, path = ''): Promise> { - assertReference(reference) - - return collection.download(this.url, reference, path) - } - - /** - * Download single file as a readable stream from collection given using the path - * - * @param reference Bee collection reference - * @param path Path of the requested file in the collection - * @param axiosOptions optional - alter default options of axios HTTP client - * - * @returns file in readable stream with metadata - */ - downloadReadableFileFromCollection( - reference: Reference | string, - path = '', - axiosOptions?: AxiosRequestConfig, - ): Promise> { - assertReference(reference) - - return collection.downloadReadable(this.url, reference, path, axiosOptions) + return bzz.uploadCollection(this.url, data, options) } /** diff --git a/src/modules/bzz.ts b/src/modules/bzz.ts new file mode 100644 index 00000000..e33d0c65 --- /dev/null +++ b/src/modules/bzz.ts @@ -0,0 +1,161 @@ +import { + Collection, + CollectionUploadOptions, + Data, + FileData, + FileUploadOptions, + Reference, + UploadHeaders, +} from '../types' +import { extractUploadHeaders, readFileHeaders } from '../utils/headers' +import { safeAxios } from '../utils/safeAxios' +import { prepareData } from '../utils/data' +import { BeeArgumentError } from '../utils/error' +import { makeTar } from '../utils/tar' +import { assertCollection } from '../utils/collection' +import { AxiosRequestConfig } from 'axios' +import { wrapBytesWithHelpers } from '../utils/bytes' +import { Readable } from 'stream' + +const bzzEndpoint = '/bzz' + +interface FileUploadHeaders extends UploadHeaders { + 'content-length'?: string + 'content-type'?: string +} + +function extractFileUploadHeaders(options?: FileUploadOptions): FileUploadHeaders { + const headers: FileUploadHeaders = extractUploadHeaders(options) + + if (options?.size) headers['content-length'] = String(options.size) + + if (options?.contentType) headers['content-type'] = options.contentType + + return headers +} +export async function uploadFile( + url: string, + data: string | Uint8Array | Readable | ArrayBuffer, + name?: string, + options?: FileUploadOptions, +): Promise { + if (!url) { + throw new BeeArgumentError('url parameter is required and cannot be empty', url) + } + + const response = await safeAxios<{ reference: Reference }>({ + ...options?.axiosOptions, + method: 'post', + url: url + bzzEndpoint, + data: prepareData(data), + headers: { + ...extractFileUploadHeaders(options), + }, + params: { name }, + responseType: 'json', + }) + + return response.data.reference +} + +/** + * Download single file as a buffer + * + * @param url Bee URL + * @param hash Bee file or collection hash + * @param path If hash is collection then this defines path to a single file in the collection + * @param axiosOptions optional - alter default options of axios HTTP client + */ +export async function downloadFile( + url: string, + hash: string, + path = '', + axiosOptions?: AxiosRequestConfig, +): Promise> { + const response = await safeAxios({ + ...axiosOptions, + method: 'GET', + responseType: 'arraybuffer', + url: `${url}${bzzEndpoint}/${hash}/${path}`, + }) + const file = { + ...readFileHeaders(response.headers), + data: wrapBytesWithHelpers(new Uint8Array(response.data)), + } + + return file +} + +/** + * Download single file as a readable stream + * + * @param url Bee URL + * @param hash Bee file or collection hash + * @param path If hash is collection then this defines path to a single file in the collection + * @param axiosOptions optional - alter default options of axios HTTP client + */ +export async function downloadFileReadable( + url: string, + hash: string, + path = '', + axiosOptions?: AxiosRequestConfig, +): Promise> { + const response = await safeAxios({ + ...axiosOptions, + method: 'GET', + responseType: 'stream', + url: `${url}${bzzEndpoint}/${hash}/${path}`, + }) + const file = { + ...readFileHeaders(response.headers), + data: response.data, + } + + return file +} + +/*******************************************************************************************************************/ +// Collections + +interface CollectionUploadHeaders extends UploadHeaders { + 'swarm-index-document'?: string + 'swarm-error-document'?: string +} + +function extractCollectionUploadHeaders(options?: CollectionUploadOptions): CollectionUploadHeaders { + const headers: CollectionUploadHeaders = extractUploadHeaders(options) + + if (options?.indexDocument) headers['swarm-index-document'] = options.indexDocument + + if (options?.errorDocument) headers['swarm-error-document'] = options.errorDocument + + return headers +} + +export async function uploadCollection( + url: string, + collection: Collection, + options?: CollectionUploadOptions, +): Promise { + if (!url) { + throw new BeeArgumentError('url parameter is required and cannot be empty', url) + } + + assertCollection(collection) + const tarData = makeTar(collection) + + const response = await safeAxios<{ reference: Reference }>({ + ...options?.axiosOptions, + method: 'post', + url: url + bzzEndpoint, + data: tarData, + responseType: 'json', + headers: { + 'content-type': 'application/x-tar', + 'swarm-collection': 'true', + ...extractCollectionUploadHeaders(options), + }, + }) + + return response.data.reference +} diff --git a/src/modules/collection.ts b/src/modules/collection.ts deleted file mode 100644 index 0e792706..00000000 --- a/src/modules/collection.ts +++ /dev/null @@ -1,209 +0,0 @@ -import type { Readable } from 'stream' -import * as fs from 'fs' -import path from 'path' - -import type { CollectionUploadOptions, Collection, FileData, UploadHeaders, Data } from '../types' -import type { AxiosRequestConfig } from 'axios' -import { makeTar } from '../utils/tar' -import { safeAxios } from '../utils/safeAxios' -import { extractUploadHeaders, readFileHeaders } from '../utils/headers' -import { BeeArgumentError } from '../utils/error' -import { fileArrayBuffer } from '../utils/file' -import { Reference } from '../types' -import { wrapBytesWithHelpers } from '../utils/bytes' - -const dirsEndpoint = '/dirs' -const bzzEndpoint = '/bzz' - -interface CollectionUploadHeaders extends UploadHeaders { - 'swarm-index-document'?: string - 'swarm-error-document'?: string -} - -function extractCollectionUploadHeaders(options?: CollectionUploadOptions): CollectionUploadHeaders { - const headers: CollectionUploadHeaders = extractUploadHeaders(options) - - if (options?.indexDocument) headers['swarm-index-document'] = options.indexDocument - - if (options?.errorDocument) headers['swarm-error-document'] = options.errorDocument - - return headers -} - -function isUint8Array(obj: unknown): obj is Uint8Array { - return obj instanceof Uint8Array -} - -function isCollection(data: unknown): data is Collection { - if (!Array.isArray(data)) { - return false - } - - return !data.some(entry => typeof entry !== 'object' || !entry.data || !entry.path || !isUint8Array(entry.data)) -} - -/** - * Creates array in the format of Collection with Readable streams prepared for upload. - * - * @param dir absolute path to the directory - * @param recursive flag that specifies if the directory should be recursively walked and get files in those directories. - */ -export function buildCollection(dir: string, recursive = true): Promise> { - return buildCollectionRelative(dir, '', recursive) -} - -async function buildCollectionRelative( - dir: string, - relativePath: string, - recursive = true, -): Promise> { - // Handles case when the dir is not existing or it is a file ==> throws an error - const dirname = path.join(dir, relativePath) - const entries = await fs.promises.opendir(dirname) - let collection: Collection = [] - - for await (const entry of entries) { - const fullPath = path.join(dir, relativePath, entry.name) - const entryPath = path.join(relativePath, entry.name) - - if (entry.isFile()) { - collection.push({ - path: entryPath, - data: new Uint8Array(await fs.promises.readFile(fullPath)), - }) - } else if (entry.isDirectory() && recursive) { - collection = [...(await buildCollectionRelative(dir, entryPath, recursive)), ...collection] - } - } - - return collection -} - -/* - * This is a workaround for fixing the type definitions - * regarding the missing `webkitRelativePath` property which is - * provided on files if you specify the `webkitdirectory` - * property on the HTML input element. This is a non-standard - * functionality supported in all major browsers. - */ -interface WebkitFile extends File { - readonly webkitRelativePath?: string -} - -function filePath(file: WebkitFile) { - if (file.webkitRelativePath && file.webkitRelativePath !== '') { - return file.webkitRelativePath.replace(/.*?\//i, '') - } - - return file.name -} - -export async function buildFileListCollection(fileList: FileList | File[]): Promise> { - const collection: Collection = [] - - for (let i = 0; i < fileList.length; i++) { - const file = fileList[i] as WebkitFile - - if (file) { - collection.push({ - path: filePath(file), - data: new Uint8Array(await fileArrayBuffer(file)), - }) - } - } - - return collection -} - -/** - * Upload collection of files to a Bee node - * - * @param url Bee URL - * @param data Data in Collection format to be uploaded - * @param options Additional options like tag, encryption, pinning - */ -export async function upload( - url: string, - data: Collection, - options?: CollectionUploadOptions, -): Promise { - if (!url || url === '') { - throw new BeeArgumentError('url parameter is required and cannot be empty', url) - } - - if (!isCollection(data)) { - throw new BeeArgumentError('invalid collection', data) - } - - const tarData = makeTar(data) - - const response = await safeAxios<{ reference: Reference }>({ - ...options?.axiosOptions, - method: 'post', - url: `${url}${dirsEndpoint}`, - data: tarData, - responseType: 'json', - headers: { - 'content-type': 'application/x-tar', - ...extractCollectionUploadHeaders(options), - }, - }) - - return response.data.reference -} - -/** - * Download single file as a buffer from Collection given using the path - * - * @param url Bee URL - * @param hash Bee Collection hash - * @param path Path of the requested file in the Collection - */ -export async function download(url: string, hash: string, path = ''): Promise> { - if (!url || url === '') { - throw new BeeArgumentError('url parameter is required and cannot be empty', url) - } - - const response = await safeAxios({ - responseType: 'arraybuffer', - url: `${url}${bzzEndpoint}/${hash}/${path}`, - }) - const file = { - ...readFileHeaders(response.headers), - data: wrapBytesWithHelpers(new Uint8Array(response.data)), - } - - return file -} - -/** - * Download single file as a buffer from Collection given using the path - * - * @param url Bee URL - * @param hash Bee Collection hash - * @param path Path of the requested file in the Collection - * @param axiosOptions optional - alter default options of axios HTTP client - */ -export async function downloadReadable( - url: string, - hash: string, - path = '', - axiosOptions?: AxiosRequestConfig, -): Promise> { - if (!url || url === '') { - throw new BeeArgumentError('url parameter is required and cannot be empty', url) - } - - const response = await safeAxios({ - responseType: 'stream', - url: `${url}${bzzEndpoint}/${hash}/${path}`, - }) - const file = { - ...axiosOptions, - method: 'GET', - ...readFileHeaders(response.headers), - data: response.data, - } - - return file -} diff --git a/src/modules/file.ts b/src/modules/file.ts deleted file mode 100644 index ed973c2f..00000000 --- a/src/modules/file.ts +++ /dev/null @@ -1,101 +0,0 @@ -import type { AxiosRequestConfig } from 'axios' -import type { Readable } from 'stream' -import { Data, FileData, FileUploadOptions, Reference, UploadHeaders } from '../types' -import { prepareData } from '../utils/data' -import { extractUploadHeaders, readFileHeaders } from '../utils/headers' -import { safeAxios } from '../utils/safeAxios' -import { wrapBytesWithHelpers } from '../utils/bytes' - -const endpoint = '/files' - -interface FileUploadHeaders extends UploadHeaders { - 'content-length'?: string - 'content-type'?: string -} - -function extractFileUploadHeaders(options?: FileUploadOptions): FileUploadHeaders { - const headers: FileUploadHeaders = extractUploadHeaders(options) - - if (options?.size) headers['content-length'] = String(options.size) - - if (options?.contentType) headers['content-type'] = options.contentType - - return headers -} - -/** - * Upload single file to a Bee node - * - * @param url Bee URL - * @param data Data to be uploaded - * @param name optional - name of the file - * @param options optional - Aditional options like tag, encryption, pinning - */ -export async function upload( - url: string, - data: string | Uint8Array | Readable | ArrayBuffer, - name?: string, - options?: FileUploadOptions, -): Promise { - const response = await safeAxios<{ reference: Reference }>({ - ...options?.axiosOptions, - method: 'post', - url: url + endpoint, - data: prepareData(data), - headers: { - ...extractFileUploadHeaders(options), - }, - responseType: 'json', - params: { name }, - }) - - return response.data.reference -} - -/** - * Download single file as a buffer - * - * @param url Bee URL - * @param hash Bee file hash - * @param axiosOptions optional - alter default options of axios HTTP client - */ -export async function download(url: string, hash: string, axiosOptions?: AxiosRequestConfig): Promise> { - const response = await safeAxios({ - ...axiosOptions, - method: 'GET', - responseType: 'arraybuffer', - url: `${url}${endpoint}/${hash}`, - }) - const file = { - ...readFileHeaders(response.headers), - data: wrapBytesWithHelpers(new Uint8Array(response.data)), - } - - return file -} - -/** - * Download single file as a readable stream - * - * @param url Bee URL - * @param hash Bee file hash - * @param axiosOptions optional - alter default options of axios HTTP client - */ -export async function downloadReadable( - url: string, - hash: string, - axiosOptions?: AxiosRequestConfig, -): Promise> { - const response = await safeAxios({ - ...axiosOptions, - method: 'GET', - responseType: 'stream', - url: `${url}${endpoint}/${hash}`, - }) - const file = { - ...readFileHeaders(response.headers), - data: response.data, - } - - return file -} diff --git a/src/utils/collection.ts b/src/utils/collection.ts new file mode 100644 index 00000000..14922c12 --- /dev/null +++ b/src/utils/collection.ts @@ -0,0 +1,92 @@ +import { Collection } from '../types' +import { BeeArgumentError } from './error' +import path from 'path' +import fs from 'fs' +import { fileArrayBuffer } from './file' + +function isUint8Array(obj: unknown): obj is Uint8Array { + return obj instanceof Uint8Array +} + +export function isCollection(data: unknown): data is Collection { + if (!Array.isArray(data)) { + return false + } + + return data.every(entry => typeof entry === 'object' && entry.data && entry.path && isUint8Array(entry.data)) +} + +export function assertCollection(data: unknown): asserts data is Collection { + if (!isCollection(data)) { + throw new BeeArgumentError('invalid collection', data) + } +} + +/** + * Creates array in the format of Collection with data loaded from directory on filesystem. + * The function loads all the data into memory! + * + * @param dir absolute path to the directory + */ +export function makeCollectionFromFS(dir: string): Promise> { + return buildCollectionRelative(dir, '') +} + +async function buildCollectionRelative(dir: string, relativePath: string): Promise> { + // Handles case when the dir is not existing or it is a file ==> throws an error + const dirname = path.join(dir, relativePath) + const entries = await fs.promises.opendir(dirname) + let collection: Collection = [] + + for await (const entry of entries) { + const fullPath = path.join(dir, relativePath, entry.name) + const entryPath = path.join(relativePath, entry.name) + + if (entry.isFile()) { + collection.push({ + path: entryPath, + data: new Uint8Array(await fs.promises.readFile(fullPath)), + }) + } else if (entry.isDirectory()) { + collection = [...(await buildCollectionRelative(dir, entryPath)), ...collection] + } + } + + return collection +} + +/* + * This is a workaround for fixing the type definitions + * regarding the missing `webkitRelativePath` property which is + * provided on files if you specify the `webkitdirectory` + * property on the HTML input element. This is a non-standard + * functionality supported in all major browsers. + */ +interface WebkitFile extends File { + readonly webkitRelativePath?: string +} + +function filePath(file: WebkitFile) { + if (file.webkitRelativePath && file.webkitRelativePath !== '') { + return file.webkitRelativePath.replace(/.*?\//i, '') + } + + return file.name +} + +export async function makeCollectionFromFileList(fileList: FileList | File[]): Promise> { + const collection: Collection = [] + + for (let i = 0; i < fileList.length; i++) { + const file = fileList[i] as WebkitFile + + if (file) { + collection.push({ + path: filePath(file), + data: new Uint8Array(await fileArrayBuffer(file)), + }) + } + } + + return collection +} diff --git a/test/integration/bee-class.spec.ts b/test/integration/bee-class.spec.ts index ba853c71..e3cacbd8 100644 --- a/test/integration/bee-class.spec.ts +++ b/test/integration/bee-class.spec.ts @@ -22,7 +22,7 @@ import { import { makeSigner } from '../../src/chunk/signer' import { makeSOCAddress, uploadSingleOwnerChunkData } from '../../src/chunk/soc' import { makeEthAddress } from '../../src/utils/eth' -import * as collection from '../../src/modules/collection' +import * as bzz from '../../src/modules/bzz' describe('Bee class', () => { const BEE_URL = beeUrl() @@ -306,7 +306,7 @@ describe('Bee class', () => { data: new TextEncoder().encode('some data'), }, ] - const cacHash = await collection.upload(BEE_URL, directoryStructure) + const cacHash = await bzz.uploadCollection(BEE_URL, directoryStructure) const feed = bee.makeFeedWriter('sequence', topic, signer) await feed.upload(cacHash) @@ -315,8 +315,8 @@ describe('Bee class', () => { expect(typeof manifestReference).toBe('string') // this calls /bzz endpoint that should resolve the manifest and the feed returning the latest feed's content - const bzz = await bee.downloadFileFromCollection(manifestReference, 'index.html') - expect(new TextDecoder().decode(bzz.data)).toEqual('some data') + const file = await bee.downloadFile(manifestReference, 'index.html') + expect(new TextDecoder().decode(file.data)).toEqual('some data') }, FEED_TIMEOUT, ) diff --git a/test/integration/modules/bzz.spec.ts b/test/integration/modules/bzz.spec.ts new file mode 100644 index 00000000..cd5a5db4 --- /dev/null +++ b/test/integration/modules/bzz.spec.ts @@ -0,0 +1,267 @@ +import * as bzz from '../../../src/modules/bzz' +import { Collection, ENCRYPTED_REFERENCE_HEX_LENGTH } from '../../../src/types' +import { beeUrl, BIG_FILE_TIMEOUT, createReadable, ERR_TIMEOUT, invalidReference, randomByteArray } from '../../utils' +import { makeCollectionFromFS } from '../../../src/utils/collection' +import * as tag from '../../../src/modules/tag' + +const BEE_URL = beeUrl() + +describe('modules/bzz', () => { + describe('collections', () => { + it('should store and retrieve collection with single file', async () => { + const directoryStructure: Collection = [ + { + path: '0', + data: Uint8Array.from([0]), + }, + ] + + const hash = await bzz.uploadCollection(BEE_URL, directoryStructure) + const file = await bzz.downloadFile(BEE_URL, hash, directoryStructure[0].path) + + expect(file.name).toEqual(directoryStructure[0].path) + expect(file.data).toEqual(directoryStructure[0].data) + }) + + it('should retrieve the filename but not the complete path', async () => { + const path = 'a/b/c/d/' + const name = '0' + const directoryStructure: Collection = [ + { + path: `${path}${name}`, + data: Uint8Array.from([0]), + }, + ] + + const hash = await bzz.uploadCollection(BEE_URL, directoryStructure) + const file = await bzz.downloadFile(BEE_URL, hash, directoryStructure[0].path) + + expect(file.name).toEqual(name) + expect(file.data).toEqual(directoryStructure[0].data) + }) + + it('should work with pinning', async () => { + const directoryStructure: Collection = [ + { + path: '0', + data: Uint8Array.from([0]), + }, + ] + + const hash = await bzz.uploadCollection(BEE_URL, directoryStructure, { pin: true }) + const file = await bzz.downloadFile(BEE_URL, hash, directoryStructure[0].path) + + expect(file.name).toEqual(directoryStructure[0].path) + expect(file.data).toEqual(directoryStructure[0].data) + }) + + it('should work with encryption', async () => { + const directoryStructure: Collection = [ + { + path: '0', + data: Uint8Array.from([0]), + }, + ] + + const hash = await bzz.uploadCollection(BEE_URL, directoryStructure, { encrypt: true }) + const file = await bzz.downloadFile(BEE_URL, hash, directoryStructure[0].path) + + expect(file.name).toEqual(directoryStructure[0].path) + expect(file.data).toEqual(directoryStructure[0].data) + expect(hash.length).toEqual(ENCRYPTED_REFERENCE_HEX_LENGTH) + }) + + it( + 'should upload bigger file', + async () => { + const directoryStructure: Collection = [ + { + path: '0', + data: new Uint8Array(32 * 1024 * 1024), + }, + ] + + const response = await bzz.uploadCollection(BEE_URL, directoryStructure) + + expect(typeof response).toEqual('string') + }, + BIG_FILE_TIMEOUT, + ) + + it('should throw error when the upload url is not set', async () => { + await expect( + bzz.uploadCollection((undefined as unknown) as string, (undefined as unknown) as []), + ).rejects.toThrowError() + }) + + it('should throw error when the upload url is not empty', async () => { + const url = '' + await expect(bzz.uploadCollection(url, (undefined as unknown) as [])).rejects.toThrowError() + }) + + it('should throw error when the collection is empty', async () => { + await expect(bzz.uploadCollection(BEE_URL, [])).rejects.toThrowError() + }) + + it('should store and retrieve collection', async () => { + const directoryStructure: Collection = [ + { + path: '0', + data: Uint8Array.from([0]), + }, + { + path: '1', + data: Uint8Array.from([1]), + }, + ] + + const hash = await bzz.uploadCollection(BEE_URL, directoryStructure) + + const file0 = await bzz.downloadFile(BEE_URL, hash, directoryStructure[0].path) + expect(file0.name).toEqual(directoryStructure[0].path) + expect(file0.data).toEqual(directoryStructure[0].data) + + const file1 = await bzz.downloadFile(BEE_URL, hash, directoryStructure[1].path) + expect(file1.name).toEqual(directoryStructure[1].path) + expect(file1.data).toEqual(directoryStructure[1].data) + }) + + it('should store and retrieve collection with index document', async () => { + const directoryStructure: Collection = [ + { + path: '0', + data: Uint8Array.from([0]), + }, + { + path: '1', + data: Uint8Array.from([1]), + }, + ] + + const hash = await bzz.uploadCollection(BEE_URL, directoryStructure, { + indexDocument: '0', + }) + + const indexFile = await bzz.downloadFile(BEE_URL, hash) + expect(indexFile.name).toEqual(directoryStructure[0].path) + expect(indexFile.data).toEqual(directoryStructure[0].data) + }) + + it('should store and retrieve collection with error document', async () => { + const directoryStructure: Collection = [ + { + path: '0', + data: Uint8Array.from([0]), + }, + { + path: '1', + data: Uint8Array.from([1]), + }, + ] + + const hash = await bzz.uploadCollection(BEE_URL, directoryStructure, { + errorDocument: '0', + }) + + const errorFile = await bzz.downloadFile(BEE_URL, hash, 'error') + expect(errorFile.name).toEqual(directoryStructure[0].path) + expect(errorFile.data).toEqual(directoryStructure[0].data) + }) + + it('should store and retrieve actual directory', async () => { + const path = 'test/data/' + const dir = `./${path}` + const file3Name = '3.txt' + const subDir = 'sub/' + const data = Uint8Array.from([51, 10]) + const directoryStructure = await makeCollectionFromFS(dir) + const hash = await bzz.uploadCollection(BEE_URL, directoryStructure) + + const file3 = await bzz.downloadFile(BEE_URL, hash, `${subDir}${file3Name}`) + expect(file3.name).toEqual(file3Name) + expect(file3.data).toEqual(data) + }) + + it('should store and retrieve actual directory with index document', async () => { + const path = 'test/data/' + const dir = `./${path}` + const fileName = '1.txt' + const data = Uint8Array.from([49, 10]) + const directoryStructure = await makeCollectionFromFS(dir) + const hash = await bzz.uploadCollection(BEE_URL, directoryStructure, { indexDocument: `${fileName}` }) + + const file1 = await bzz.downloadFile(BEE_URL, hash) + expect(file1.name).toEqual(fileName) + expect(file1.data).toEqual(data) + }) + }) + + describe('file', () => { + it('should store and retrieve file', async () => { + const data = 'hello world' + const filename = 'hello.txt' + + const hash = await bzz.uploadFile(BEE_URL, data, filename) + const fileData = await bzz.downloadFile(BEE_URL, hash) + + expect(Buffer.from(fileData.data).toString()).toEqual(data) + expect(fileData.name).toEqual(filename) + }) + + it('should store file without filename', async () => { + const data = 'hello world' + + const hash = await bzz.uploadFile(BEE_URL, data) + const fileData = await bzz.downloadFile(BEE_URL, hash) + + expect(Buffer.from(fileData.data).toString()).toEqual(data) + }) + + it('should store readable file', async () => { + const data = randomByteArray(5000, 0) + const filename = 'hello.txt' + + const hash = await bzz.uploadFile(BEE_URL, createReadable(data), filename, { + size: data.length, + }) + const fileData = await bzz.downloadFile(BEE_URL, hash) + + expect(fileData.data).toEqual(data) + }) + + it('should store file with a tag', async () => { + // Relates to how many chunks is uploaded which depends on manifest serialization. + // https://github.com/ethersphere/bee/pull/1501#discussion_r611385602 + const EXPECTED_TAGS_COUNT = 6 + + const data = randomByteArray(5000, 1) + const filename = 'hello.txt' + + const tag1 = await tag.createTag(BEE_URL) + await bzz.uploadFile(BEE_URL, data, filename, { tag: tag1.uid }) + const tag2 = await tag.retrieveTag(BEE_URL, tag1) + + expect(tag2.total).toEqual(EXPECTED_TAGS_COUNT) + expect(tag2.processed).toEqual(EXPECTED_TAGS_COUNT) + }, 5000) + + it( + 'should catch error', + async () => { + await expect(bzz.downloadFile(BEE_URL, invalidReference)).rejects.toThrow('Not Found') + }, + ERR_TIMEOUT, + ) + + it( + 'should upload bigger file', + async () => { + const data = new Uint8Array(32 * 1024 * 1024) + const response = await bzz.uploadFile(BEE_URL, data) + + expect(typeof response).toEqual('string') + }, + BIG_FILE_TIMEOUT, + ) + }) +}) diff --git a/test/integration/modules/collection.spec.ts b/test/integration/modules/collection.spec.ts deleted file mode 100644 index 3584125a..00000000 --- a/test/integration/modules/collection.spec.ts +++ /dev/null @@ -1,194 +0,0 @@ -import * as collection from '../../../src/modules/collection' -import { Collection, ENCRYPTED_REFERENCE_HEX_LENGTH } from '../../../src/types' -import { beeUrl, BIG_FILE_TIMEOUT } from '../../utils' - -const BEE_URL = beeUrl() - -describe('modules/collection', () => { - it('should store and retrieve collection with single file', async () => { - const directoryStructure: Collection = [ - { - path: '0', - data: Uint8Array.from([0]), - }, - ] - - const hash = await collection.upload(BEE_URL, directoryStructure) - const file = await collection.download(BEE_URL, hash, directoryStructure[0].path) - - expect(file.name).toEqual(directoryStructure[0].path) - expect(file.data).toEqual(directoryStructure[0].data) - }) - - it('should retrieve the filename but not the complete path', async () => { - const path = 'a/b/c/d/' - const name = '0' - const directoryStructure: Collection = [ - { - path: `${path}${name}`, - data: Uint8Array.from([0]), - }, - ] - - const hash = await collection.upload(BEE_URL, directoryStructure) - const file = await collection.download(BEE_URL, hash, directoryStructure[0].path) - - expect(file.name).toEqual(name) - expect(file.data).toEqual(directoryStructure[0].data) - }) - - it('should work with pinning', async () => { - const directoryStructure: Collection = [ - { - path: '0', - data: Uint8Array.from([0]), - }, - ] - - const hash = await collection.upload(BEE_URL, directoryStructure, { pin: true }) - const file = await collection.download(BEE_URL, hash, directoryStructure[0].path) - - expect(file.name).toEqual(directoryStructure[0].path) - expect(file.data).toEqual(directoryStructure[0].data) - }) - - it('should work with encryption', async () => { - const directoryStructure: Collection = [ - { - path: '0', - data: Uint8Array.from([0]), - }, - ] - - const hash = await collection.upload(BEE_URL, directoryStructure, { encrypt: true }) - const file = await collection.download(BEE_URL, hash, directoryStructure[0].path) - - expect(file.name).toEqual(directoryStructure[0].path) - expect(file.data).toEqual(directoryStructure[0].data) - expect(hash.length).toEqual(ENCRYPTED_REFERENCE_HEX_LENGTH) - }) - - it( - 'should upload bigger file', - async () => { - const directoryStructure: Collection = [ - { - path: '0', - data: new Uint8Array(32 * 1024 * 1024), - }, - ] - - const response = await collection.upload(BEE_URL, directoryStructure) - - expect(typeof response).toEqual('string') - }, - BIG_FILE_TIMEOUT, - ) - - it('should throw error when the upload url is not set', async () => { - await expect( - collection.upload((undefined as unknown) as string, (undefined as unknown) as []), - ).rejects.toThrowError() - }) - - it('should throw error when the upload url is not empty', async () => { - const url = '' - await expect(collection.upload(url, (undefined as unknown) as [])).rejects.toThrowError() - }) - - it('should throw error when the collection is empty', async () => { - await expect(collection.upload(BEE_URL, [])).rejects.toThrowError() - }) - - it('should store and retrieve collection', async () => { - const directoryStructure: Collection = [ - { - path: '0', - data: Uint8Array.from([0]), - }, - { - path: '1', - data: Uint8Array.from([1]), - }, - ] - - const hash = await collection.upload(BEE_URL, directoryStructure) - - const file0 = await collection.download(BEE_URL, hash, directoryStructure[0].path) - expect(file0.name).toEqual(directoryStructure[0].path) - expect(file0.data).toEqual(directoryStructure[0].data) - - const file1 = await collection.download(BEE_URL, hash, directoryStructure[1].path) - expect(file1.name).toEqual(directoryStructure[1].path) - expect(file1.data).toEqual(directoryStructure[1].data) - }) - - it('should store and retrieve collection with index document', async () => { - const directoryStructure: Collection = [ - { - path: '0', - data: Uint8Array.from([0]), - }, - { - path: '1', - data: Uint8Array.from([1]), - }, - ] - - const hash = await collection.upload(BEE_URL, directoryStructure, { - indexDocument: '0', - }) - - const indexFile = await collection.download(BEE_URL, hash) - expect(indexFile.name).toEqual(directoryStructure[0].path) - expect(indexFile.data).toEqual(directoryStructure[0].data) - }) - - it('should store and retrieve collection with error document', async () => { - const directoryStructure: Collection = [ - { - path: '0', - data: Uint8Array.from([0]), - }, - { - path: '1', - data: Uint8Array.from([1]), - }, - ] - - const hash = await collection.upload(BEE_URL, directoryStructure, { - errorDocument: '0', - }) - - const errorFile = await collection.download(BEE_URL, hash, 'error') - expect(errorFile.name).toEqual(directoryStructure[0].path) - expect(errorFile.data).toEqual(directoryStructure[0].data) - }) - - it('should store and retrieve actual directory', async () => { - const path = 'test/data/' - const dir = `./${path}` - const file3Name = '3.txt' - const subDir = 'sub/' - const data = Uint8Array.from([51, 10]) - const directoryStructure = await collection.buildCollection(dir) - const hash = await collection.upload(BEE_URL, directoryStructure) - - const file3 = await collection.download(BEE_URL, hash, `${subDir}${file3Name}`) - expect(file3.name).toEqual(file3Name) - expect(file3.data).toEqual(data) - }) - - it('should store and retrieve actual directory with index document', async () => { - const path = 'test/data/' - const dir = `./${path}` - const fileName = '1.txt' - const data = Uint8Array.from([49, 10]) - const directoryStructure = await collection.buildCollection(dir) - const hash = await collection.upload(BEE_URL, directoryStructure, { indexDocument: `${fileName}` }) - - const file1 = await collection.download(BEE_URL, hash) - expect(file1.name).toEqual(fileName) - expect(file1.data).toEqual(data) - }) -}) diff --git a/test/integration/modules/file.spec.ts b/test/integration/modules/file.spec.ts deleted file mode 100644 index 132d05f4..00000000 --- a/test/integration/modules/file.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import * as file from '../../../src/modules/file' -import * as tag from '../../../src/modules/tag' -import { beeUrl, BIG_FILE_TIMEOUT, createReadable, ERR_TIMEOUT, invalidReference, randomByteArray } from '../../utils' - -const BEE_URL = beeUrl() - -describe('modules/file', () => { - it('should store and retrieve file', async () => { - const data = 'hello world' - const filename = 'hello.txt' - - const hash = await file.upload(BEE_URL, data, filename) - const fileData = await file.download(BEE_URL, hash) - - expect(Buffer.from(fileData.data).toString()).toEqual(data) - expect(fileData.name).toEqual(filename) - }) - - it('should store file without filename', async () => { - const data = 'hello world' - - const hash = await file.upload(BEE_URL, data) - const fileData = await file.download(BEE_URL, hash) - - expect(Buffer.from(fileData.data).toString()).toEqual(data) - }) - - it('should store readable file', async () => { - const data = randomByteArray(5000, 0) - const filename = 'hello.txt' - - const hash = await file.upload(BEE_URL, createReadable(data), filename, { - size: data.length, - }) - const fileData = await file.download(BEE_URL, hash) - - expect(fileData.data).toEqual(data) - }) - - it('should store file with a tag', async () => { - const data = randomByteArray(5000, 1) - const filename = 'hello.txt' - - const tag1 = await tag.createTag(BEE_URL) - await file.upload(BEE_URL, data, filename, { tag: tag1.uid }) - const tag2 = await tag.retrieveTag(BEE_URL, tag1) - - expect(tag2.total).toEqual(5) - expect(tag2.processed).toEqual(5) - }, 5000) - - it( - 'should catch error', - async () => { - await expect(file.download(BEE_URL, invalidReference)).rejects.toThrow('Not Found') - }, - ERR_TIMEOUT, - ) - - it( - 'should upload bigger file', - async () => { - const data = new Uint8Array(32 * 1024 * 1024) - const response = await file.upload(BEE_URL, data) - - expect(typeof response).toEqual('string') - }, - BIG_FILE_TIMEOUT, - ) -}) diff --git a/test/integration/modules/pinning.spec.ts b/test/integration/modules/pinning.spec.ts index eb15662f..f591fc6c 100644 --- a/test/integration/modules/pinning.spec.ts +++ b/test/integration/modules/pinning.spec.ts @@ -1,6 +1,5 @@ import * as pinning from '../../../src/modules/pinning' -import * as file from '../../../src/modules/file' -import * as collection from '../../../src/modules/collection' +import * as bzz from '../../../src/modules/bzz' import * as bytes from '../../../src/modules/bytes' import * as chunk from '../../../src/modules/chunk' import { @@ -21,14 +20,14 @@ describe('modules/pin', () => { const randomData = randomByteArray(5000) it('should pin an existing file', async () => { - const hash = await file.upload(BEE_URL, randomData) + const hash = await bzz.uploadFile(BEE_URL, randomData) const response = await pinning.pinFile(BEE_URL, hash) expect(response).toEqual(okResponse) }) it('should unpin an existing file', async () => { - const hash = await file.upload(BEE_URL, randomData) + const hash = await bzz.uploadFile(BEE_URL, randomData) const response = await pinning.unpinFile(BEE_URL, hash) expect(response).toEqual(okResponse) @@ -60,14 +59,14 @@ describe('modules/pin', () => { ] it('should pin an existing collection', async () => { - const hash = await collection.upload(BEE_URL, testCollection) + const hash = await bzz.uploadCollection(BEE_URL, testCollection) const response = await pinning.pinCollection(BEE_URL, hash) expect(response).toEqual(okResponse) }) it('should unpin an existing collections', async () => { - const hash = await collection.upload(BEE_URL, testCollection) + const hash = await bzz.uploadCollection(BEE_URL, testCollection) const response = await pinning.unpinCollection(BEE_URL, hash) expect(response).toEqual(okResponse)