UNPKG

@adobe/aio-lib-files

Version:

An abstraction on top of blob cloud storage exposing a file like API

289 lines (243 loc) 12.3 kB
/* Copyright 2019 Adobe. All rights reserved. This file is licensed to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /* ************* NOTE 1: these tests must be run sequentially, jest does it by default within a SINGLE file ************* */ /* ************* NOTE 2: requires env vars TEST_AUTH_1, TEST_NS_1 and TEST_AUTH_2, TEST_NS_2 for 2 different namespaces. ************* */ const fs = require('fs-extra') const upath = require('upath') const TEST_DIR = '.e2e' // do not end with '/' const fullPath = p => upath.toUnix(upath.join(TEST_DIR, p)) const filesLib = require('../index') const fetch = require('node-fetch').default const testFile = 'e2e_test_file' const testContent = 'hello world' jest.setTimeout(60000) // one minute per test beforeEach(() => { expect.hasAssertions() }) const initFilesEnv = async (n = 1) => { delete process.env.__OW_API_KEY delete process.env.__OW_NAMESPACE process.env.__OW_API_KEY = process.env[`TEST_AUTH_${n}`] process.env.__OW_NAMESPACE = process.env[`TEST_NAMESPACE_${n}`] // 1. init will fetch credentials from the tvm using ow creds const files = await filesLib.init() // { tvm: { cacheFile: false } } // keep cache for better perf? // make sure we delete all files, this might fail as it is an operation under test await files.delete('/') return files } const readStream = (stream) => { return new Promise((resolve, reject) => { const chunks = [] stream.on('data', data => { chunks.push(data) }) stream.on('end', () => { resolve(Buffer.concat(chunks)) }) stream.on('error', reject) }) } const expectToThrowCode = async (promise, code) => { let err try { await promise } catch (e) { expect({ name: e.name, code: e.code, message: e.message, sdkDetails: e.sdkDetails }).toEqual(expect.objectContaining({ name: 'FilesLibError', code })) err = e } expect(err).toBeInstanceOf(Error) } const testACL = async (isPublic = false) => { const files = await initFilesEnv() const testFile = isPublic ? 'public/e2e/test/file' : 'e2e/test/file' await files.write(testFile, testContent) const props = await files.getProperties(testFile) expect(props).toEqual(expect.objectContaining({ isPublic: isPublic, isDirectory: false, url: expect.any(String) })) const response = await fetch(props.url) expect(response.ok).toEqual(isPublic) if (isPublic) { expect(await response.text()).toEqual(testContent) } await files.delete(testFile) } describe('e2e tests using OpenWhisk credentials (as env vars)', () => { test('error bad credentials test: auth is ok but namespace is not', async () => { delete process.env.__OW_API_KEY delete process.env.__OW_NAMESPACE process.env.__OW_API_KEY = process.env.TEST_AUTH_1 process.env.__OW_NAMESPACE = process.env.TEST_NAMESPACE_1 + 'bad' try { await filesLib.init() } catch (e) { expect({ name: e.name, code: e.code, message: e.message, sdkDetails: e.sdkDetails }).toEqual(expect.objectContaining({ name: 'FilesLibError', code: 'ERROR_BAD_CREDENTIALS' })) } }) test('read-write-delete basic test using one file: read, write, read, delete, read', async () => { const files = await initFilesEnv() // 1. read file that does not exist await expectToThrowCode(files.read(testFile), 'ERROR_FILE_NOT_EXISTS') // 2. read file that exists expect(await files.write(testFile, testContent)).toEqual(testContent.length) // check write return value expect((await files.read(testFile)).toString()).toEqual(testContent) // 3. read deleted file expect(await files.delete(testFile)).toEqual([testFile]) await expectToThrowCode(files.read(testFile), 'ERROR_FILE_NOT_EXISTS') }) test('read with position and length: write, read subset of file content, read length bigger than size, read from position bigger than size (error)', async () => { const files = await initFilesEnv() await files.write(testFile, testContent) expect((await files.read(testFile, { position: 3, length: 4 })).toString()).toEqual('lo w') expect((await files.read(testFile, { length: 1000 })).toString()).toEqual(testContent) await expectToThrowCode(files.read(testFile, { position: 1000 }), 'ERROR_OUT_OF_RANGE') }) test('private access test: write private file, get props, fetch url', async () => { await testACL(false) }) test('public access test: write public file, get props, fetch url', async () => { await testACL(true) }) test('list tests: list empty root, write public and private files, list various public/private subdirs, delete', async () => { const files = await initFilesEnv() const testFiles = ['e2e/test/file.txt', 'e2e/testfile.txt', 'e2etestfile.txt', 'public/e2e/test/file.txt', 'public/e2e/testfile.txt', 'public/e2etestfile.txt', 'publice2etestfile.txt'].sort() // 1. list empty root expect(await files.list('/')).toEqual([]) // 2. list file that doesn't exist expect(await files.list('afile')).toEqual([]) // 3. list various public and private subfolders after adding files await Promise.all(testFiles.map(f => files.write(f, testContent))) /// 3.a. list root expect((await files.list('/')).sort()).toEqual(testFiles) /// 3.b list public files expect((await files.list('public')).sort()).toEqual(['public/e2e/test/file.txt', 'public/e2e/testfile.txt', 'public/e2etestfile.txt'].sort()) /// 3.c.i list public files subdir 1 expect((await files.list('public/e2e/')).sort()).toEqual(['public/e2e/test/file.txt', 'public/e2e/testfile.txt'].sort()) /// 3.c.ii list public files subdir 2 expect((await files.list('public/e2e/test/')).sort()).toEqual(['public/e2e/test/file.txt'].sort()) /// 3.d.i list e2e/ (private subdir) expect((await files.list('e2e/')).sort()).toEqual(['e2e/test/file.txt', 'e2e/testfile.txt'].sort()) /// 3.d.iii list e2e/test (private subdir) expect((await files.list('e2e/test/')).sort()).toEqual(['e2e/test/file.txt']) /// 3.e. list a (private) root file expect((await files.list('publice2etestfile.txt')).sort()).toEqual(['publice2etestfile.txt'].sort()) /// 3.f. list a subfolder w/o ending with / => means file that does not exist expect((await files.list('e2e/test')).sort()).toEqual([]) // 4. delete all files expect((await files.delete('/')).sort()).toEqual(testFiles) }) test('stream test: write a readStream, create readStream, read the stream, expect content to be same', async () => { const files = await initFilesEnv() // 0. prepare read stream const Readable = require('stream').Readable const testStream = new Readable() testStream.push(testContent) testStream.push(null) // 1. write to remote file, check written size expect(await files.write(testFile, testStream)).toEqual(testContent.length) // 2. create readStream and read const s = await files.createReadStream(testFile) expect((await readStream(s)).toString()).toEqual(testContent) }) test('isolation test: read, write, delete same file for two namespaces do not interfere', async () => { const files1 = await initFilesEnv(1) const files2 = await initFilesEnv(2) const testContent1 = 'e2e ONE' const testContent2 = 'e2e TWO' // 1. test that ns2 cannot read file in ns1 await files1.write(testFile, testContent1) await expectToThrowCode(files2.read(testFile), 'ERROR_FILE_NOT_EXISTS') // 2. test that ns2 cannot update file in ns1 await files2.write(testFile, testContent2) expect((await files1.read(testFile)).toString()).toEqual(testContent1) // 3. test that ns1 cannot delete file in ns2 await files1.delete(testFile) expect((await files2.read(testFile)).toString()).toEqual(testContent2) // cleanup delete ns2 file await files2.delete(testFile) }) test('copy dir local to remote to remote to local test', async () => { const files = await initFilesEnv() const testFiles = [ 'e2e/test/file.txt', 'e2e/test/file2.txt', 'e2e/test/file3.txt', 'e2e/testfile.txt', 'e2e/testfile2.txt', 'e2e/testfile3.txt', 'e2etestfile.txt', 'e2etestfile2.txt', 'e2etestfile3.txt' ].sort() // prepare files locally await fs.emptyDir(TEST_DIR) await fs.ensureDir(TEST_DIR + '/e2e/test/') const resFiles = testFiles.map(f => fullPath(f)) await Promise.all(resFiles.map(f => fs.writeFile(f, testContent))) // 1. copy local dir to remote let map = await files.copy(TEST_DIR, '', { localSrc: true }) expect(Object.keys(map).length).toEqual(resFiles.length) expect((await files.list('/')).sort()).toEqual(resFiles) // 2. copy remote to remote map = await files.copy(TEST_DIR + '/', 'otherDir/') expect(Object.keys(map).length).toEqual(resFiles.length) expect((await files.list('otherDir/')).sort()).toEqual(resFiles.map(f => 'otherDir/' + f)) // 3. copy from remote to local map = await files.copy(TEST_DIR + '/', TEST_DIR + '/received/', { localDest: true }) expect(Object.keys(map).length).toEqual(resFiles.length) expect(await fs.readdir(TEST_DIR + '/received/.e2e')).toEqual(['e2e', 'e2etestfile.txt', 'e2etestfile2.txt', 'e2etestfile3.txt']) expect(await fs.readdir(TEST_DIR + '/received/.e2e/e2e')).toEqual(['test', 'testfile.txt', 'testfile2.txt', 'testfile3.txt']) await fs.remove(TEST_DIR) await files.delete('/') }) test('copy overwrite test local->remote->remote->local', async () => { const files = await initFilesEnv() // setup await fs.emptyDir(TEST_DIR) const fromFile = fullPath(testFile + 'src') const toOverwrite = fullPath(testFile) const toOverwrite2 = fullPath(testFile + '2') const newContent = 'some new content' await fs.writeFile(fromFile, newContent) await fs.writeFile(toOverwrite, testContent) await files.write(toOverwrite, testContent) await files.write(toOverwrite2, testContent) // 1.a. copy overwrite not allowed local -> remote let map = await files.copy(fromFile, toOverwrite, { localSrc: true, noOverwrite: true }) expect(Object.keys(map).length).toEqual(0) // 0 file copied expect((await files.read(toOverwrite)).toString()).toEqual(testContent) // old content // 1.b this time with overwrite allowed map = await files.copy(fromFile, toOverwrite, { localSrc: true, noOverwrite: false }) expect(Object.keys(map).length).toEqual(1) // 1 file copied expect((await files.read(toOverwrite)).toString()).toEqual(newContent) // new Content // 2.a copy overwrite not allowed remote -> remote // toOverwrite (remote) has the new content map = await files.copy(toOverwrite, toOverwrite2, { noOverwrite: true }) expect(Object.keys(map).length).toEqual(0) expect((await files.read(toOverwrite2)).toString()).toEqual(testContent) // 2.b this time with overwrite allowed map = await files.copy(toOverwrite, toOverwrite2, { noOverwrite: false }) expect(Object.keys(map).length).toEqual(1) // 1 file copied expect((await files.read(toOverwrite2)).toString()).toEqual(newContent) // 3.a copy overwrite not allowed remote -> local map = await files.copy(toOverwrite, toOverwrite, { localDest: true, noOverwrite: true }) expect(Object.keys(map).length).toEqual(0) expect((await fs.readFile(toOverwrite)).toString()).toEqual(testContent) // 3.b this time with overwrite allowed map = await files.copy(toOverwrite, toOverwrite, { localDest: true, noOverwrite: false }) expect(Object.keys(map).length).toEqual(1) // 1 file copied expect((await fs.readFile(toOverwrite)).toString()).toEqual(newContent) await fs.remove(TEST_DIR) await files.delete('/') }) })