From 26b5a31af08f8af44d66e269eb03fadc2ebeee8b Mon Sep 17 00:00:00 2001 From: jeremy Date: Sat, 28 Sep 2019 21:55:04 -0700 Subject: [PATCH 1/2] add screenshot upload --- lib/basset.js | 31 ++++++++++++++++++++++++++++--- lib/client.js | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/lib/basset.js b/lib/basset.js index aa89981..6227691 100644 --- a/lib/basset.js +++ b/lib/basset.js @@ -7,12 +7,13 @@ const { generateFileHash, generateHash } = require('./generate-hash'); const { getAssets } = require('./assets'); class Basset { - constructor(token, staticDir, bassetUrl, {baseUrl = '', ignoreExtensions = ''}) { + constructor(token, staticDir, bassetUrl, {baseUrl = '', ignoreExtensions = '', type = 'web'}) { this.token = token; this.staticDir = staticDir; this.baseUrl = baseUrl; this.client = new Client(bassetUrl, token); this.ignoreExtensions = ignoreExtensions.split(',').map(e => e.trim()).filter(e => e !== ''); + this.type = type; } async buildStart(compareBranch=null) { @@ -24,7 +25,9 @@ class Basset { compareBranch, assets: currentAssets, }); - await this.uploadAssets(assets); + if (this.type === 'web') { + await this.uploadAssets(assets); + } } async buildFinish() { @@ -34,7 +37,10 @@ class Basset { return this.client.buildFinish(); } - getAssets() { + async getAssets() { + if (this.type === 'web') { + return []; + } return getAssets(this.staticDir, this.baseUrl, this.ignoreExtensions); } @@ -53,6 +59,9 @@ class Basset { } async uploadSnapshotSource(snapshot, source) { + if (!this.type !== 'web') { + throw new Error('Only projects that are type web can upload snapshots'); + } if (!this.client.buildId) { throw new Error('You cannot upload snapshots without starting a build'); } @@ -62,6 +71,9 @@ class Basset { } async uploadSnapshotFile(snapshot, filePath) { + if (!this.type !== 'web') { + throw new Error('Only projects that are type web can upload snapshots'); + } if (!this.client.buildId) { throw new Error('You cannot upload snapshots without starting a build'); } @@ -70,6 +82,19 @@ class Basset { const file = fs.createReadStream(filePath); await this.client.uploadSnapshot(snapshot, relativePath, sha, file); } + + async uploadScreenshotFile({ title }, filePath) { + if (!this.type !== 'image') { + throw new Error('Only projects that are type image can upload screenshots'); + } + if (!this.client.buildId) { + throw new Error('You cannot upload screenshots without starting a build'); + } + const sha = await generateFileHash(filePath); + const relativePath = `${snapshot.title}${path.extname(filePath)}`; + const file = fs.createReadStream(filePath); + await this.client.uploadScreenshot({ title, }, relativePath, sha, file); + } } module.exports = Basset; diff --git a/lib/client.js b/lib/client.js index c80e321..e422681 100644 --- a/lib/client.js +++ b/lib/client.js @@ -83,13 +83,42 @@ class Client { browsers: browsers || '', }, }); - const { uploaded } = JSON.parse(body); // body is json + const { uploaded } = JSON.parse(body); if (!uploaded) { throw new Error(`There was a problem uploading this snapshot: ${title}`); } console.log(`Uploaded snapshot: ${title} widths: (${widths})`); } + async uploadSnapshot({ title }, relativePath, sha, file) { + const { title } = snapshot; + const { body, statusCode, statusMessage } = await this.request({ + url: `${this.bassetUrl}/build/upload/snapshot`, + method: 'POST', + headers: { + authorization: `Token ${this.token}`, + 'x-build-id': this.buildId, + 'x-relative-path': relativePath, + 'x-sha': sha, + }, + formData: { + screenshot: file, + widths: '', + title, + selectors: '', + hideSelectors: '', + browsers: '', + type: 'image', + }, + }); + const { uploaded } = JSON.parse(body); + if (!uploaded) { + throw new Error(`There was a problem uploading this screenshot: ${title}`); + } + console.log(`Uploaded screenshot: ${title}`); + } + + async uploadAsset(relativePath, sha, fileStream) { const { body, statusCode, statusMessage } = await this.request({ url: `${this.bassetUrl}/build/upload/asset`, @@ -104,7 +133,7 @@ class Client { asset: fileStream, }, }); - const { uploaded } = JSON.parse(body); // body is json + const { uploaded } = JSON.parse(body); if (!uploaded) { throw new Error( `There was a problem uploading this asset: ${relativePath}`, From d3a9d62dc43cd7f1f954475807b598aadd51dce9 Mon Sep 17 00:00:00 2001 From: jeremy Date: Sat, 26 Oct 2019 15:35:33 -0700 Subject: [PATCH 2/2] update tests --- lib/assets.js | 16 +++++++++------- lib/basset.js | 14 ++++++-------- lib/client.js | 11 ++--------- tests/assets.spec.js | 33 +++++++++++++++++---------------- tests/client.spec.js | 31 ++++++++++++++++++++++++++++++- 5 files changed, 64 insertions(+), 41 deletions(-) diff --git a/lib/assets.js b/lib/assets.js index ed93074..d628f6f 100644 --- a/lib/assets.js +++ b/lib/assets.js @@ -2,6 +2,7 @@ const fs = require('fs'); const path = require('path'); const util = require('util'); const readdir = util.promisify(fs.readdir); +const stat = util.promisify(fs.stat); const { generateFileHash } = require('./generate-hash'); @@ -12,16 +13,17 @@ const getAssets = (staticDir, relativeDir, ignoreExtensions) => { const walk = async (staticDir, relativeDir, ignoreExtensions) => { const dir = path.join(process.cwd(), staticDir, relativeDir); let assets = {}; - const files = await readdir(dir, { withFileTypes: true }); + const files = await readdir(dir); const filteredFilters = files.filter( - f => !ignoreExtensions.includes(path.extname(f.name)), + f => !ignoreExtensions.includes(path.extname(f)), ); - for await (const file of filteredFilters) { - const filePath = path.join(dir, file.name); - if (file.isDirectory()) { + for (const file of filteredFilters) { + const filePath = path.join(dir, file); + const fileState = await stat(filePath); + if (fileState.isDirectory()) { const subAssets = await walk( staticDir, - path.join(relativeDir, file.name), + path.join(relativeDir, file), ignoreExtensions ); assets = { @@ -32,7 +34,7 @@ const walk = async (staticDir, relativeDir, ignoreExtensions) => { } const sha = await generateFileHash(filePath); - const relativePath = path.join(relativeDir, file.name); + const relativePath = path.join(relativeDir, file); assets[relativePath] = sha; } return assets; diff --git a/lib/basset.js b/lib/basset.js index 6227691..639b901 100644 --- a/lib/basset.js +++ b/lib/basset.js @@ -19,7 +19,6 @@ class Basset { async buildStart(compareBranch=null) { const currentAssets = await this.getAssets(); const gitInfo = await getGitInfo(); - const { assets } = await this.client.buildStart({ ...gitInfo, compareBranch, @@ -38,7 +37,7 @@ class Basset { } async getAssets() { - if (this.type === 'web') { + if (this.type === 'image') { return []; } return getAssets(this.staticDir, this.baseUrl, this.ignoreExtensions); @@ -48,7 +47,7 @@ class Basset { if (!this.client.buildId) { throw new Error('You cannot upload assets without starting a build'); } - for await (const [filePath, sha] of Object.entries(assets)) { + for (const [filePath, sha] of Object.entries(assets)) { const relativePath = path.join(this.baseUrl, filePath); const fileStream = fs.createReadStream( path.join(this.staticDir, filePath), @@ -59,7 +58,7 @@ class Basset { } async uploadSnapshotSource(snapshot, source) { - if (!this.type !== 'web') { + if (this.type !== 'web') { throw new Error('Only projects that are type web can upload snapshots'); } if (!this.client.buildId) { @@ -71,7 +70,7 @@ class Basset { } async uploadSnapshotFile(snapshot, filePath) { - if (!this.type !== 'web') { + if (this.type !== 'web') { throw new Error('Only projects that are type web can upload snapshots'); } if (!this.client.buildId) { @@ -83,7 +82,7 @@ class Basset { await this.client.uploadSnapshot(snapshot, relativePath, sha, file); } - async uploadScreenshotFile({ title }, filePath) { + async uploadImageFile({ title }, filePath) { if (!this.type !== 'image') { throw new Error('Only projects that are type image can upload screenshots'); } @@ -91,9 +90,8 @@ class Basset { throw new Error('You cannot upload screenshots without starting a build'); } const sha = await generateFileHash(filePath); - const relativePath = `${snapshot.title}${path.extname(filePath)}`; const file = fs.createReadStream(filePath); - await this.client.uploadScreenshot({ title, }, relativePath, sha, file); + await this.client.uploadImage({ title, }, sha, file); } } diff --git a/lib/client.js b/lib/client.js index e422681..bddd005 100644 --- a/lib/client.js +++ b/lib/client.js @@ -90,7 +90,7 @@ class Client { console.log(`Uploaded snapshot: ${title} widths: (${widths})`); } - async uploadSnapshot({ title }, relativePath, sha, file) { + async uploadImage(snapshot, sha, file) { const { title } = snapshot; const { body, statusCode, statusMessage } = await this.request({ url: `${this.bassetUrl}/build/upload/snapshot`, @@ -98,17 +98,10 @@ class Client { headers: { authorization: `Token ${this.token}`, 'x-build-id': this.buildId, - 'x-relative-path': relativePath, 'x-sha': sha, }, formData: { - screenshot: file, - widths: '', - title, - selectors: '', - hideSelectors: '', - browsers: '', - type: 'image', + image: file, }, }); const { uploaded } = JSON.parse(body); diff --git a/tests/assets.spec.js b/tests/assets.spec.js index 7619311..55d995e 100644 --- a/tests/assets.spec.js +++ b/tests/assets.spec.js @@ -1,19 +1,7 @@ -const assets = require('../lib/assets'); +jest.mock('fs'); +const fs = require('fs'); -let mockReaddir = [ - { name: '1.png', isDirectory: () => false }, - { name: '2.png', isDirectory: () => false }, - { name: '3.png', isDirectory: () => false }, - { name: '4.png', isDirectory: () => false }, -]; -jest.mock('fs', () => ({ - readdir: (d, opt, cb) => { - if (d.includes('dir')) { - return cb(null, [{ name: 'test.png', isDirectory: () => false }]); - } - return cb(null, mockReaddir); - }, -})); +const assets = require('../lib/assets'); jest.mock('../lib/generate-hash', () => ({ generateHash: jest.fn(() => 'sha'), @@ -21,6 +9,13 @@ jest.mock('../lib/generate-hash', () => ({ })); test('walk returns a list of assets', async () => { + fs.stat.mockImplementation((p, cb) => (cb(null, { isDirectory: () => false }))); + fs.readdir.mockImplementation((p, cb) => cb(null, [ + '1.png', + '2.png', + '3.png', + '4.png', + ])); const data = await assets.walk('/', 'baseUrl', []); expect(data).toEqual( expect.objectContaining({ @@ -33,7 +28,13 @@ test('walk returns a list of assets', async () => { }); test('walk recurisvely searches directories', async () => { - mockReaddir = [{ name: 'dir', isDirectory: () => true }]; + fs.readdir + .mockImplementationOnce((p, cb) => cb(null, ['dir'])) + .mockImplementationOnce((p, cb) => cb(null, ['test.png'])); + + fs.stat + .mockImplementationOnce((p, cb) => (cb(null, { isDirectory: () => true }))) + .mockImplementationOnce((p, cb) => cb(null, { isDirectory: () => false })); const data = await assets.walk('/', 'baseUrl', []); expect(data).toEqual( expect.objectContaining({ diff --git a/tests/client.spec.js b/tests/client.spec.js index 2fd8560..4a43e69 100644 --- a/tests/client.spec.js +++ b/tests/client.spec.js @@ -98,7 +98,7 @@ test('uploadSnapshot', async () => { body: '{"uploaded": true}', }), ); - const result = await client.uploadSnapshot( + await client.uploadSnapshot( { title: 'title' }, 'relativePath', 'sha', @@ -106,6 +106,35 @@ test('uploadSnapshot', async () => { ); }); + +test('uploadImage', async () => { + const client = new Client('bassetUrl', 'token'); + client.request = jest.fn(() => + Promise.resolve({ + body: '{"uploaded": false}', + }), + ); + try { + await client.uploadImage( + { title: 'title' }, + 'sha', + 'file', + ); + } catch (error) { + expect(error.message).toContain('problem uploading'); + } + client.request = jest.fn(() => + Promise.resolve({ + body: '{"uploaded": true}', + }), + ); + await client.uploadSnapshot( + { title: 'title' }, + 'sha', + 'file', + ); +}); + test('uploadAsset', async () => { const client = new Client('bassetUrl', 'token'); client.request = jest.fn(() =>