diff --git a/test/tools/floodbox/bulk-action.test.js b/test/tools/floodbox/bulk-action.test.js new file mode 100644 index 0000000000..6e37bb86dd --- /dev/null +++ b/test/tools/floodbox/bulk-action.test.js @@ -0,0 +1,75 @@ +import { expect } from '@esm-bundle/chai'; +import sinon from 'sinon'; +import previewOrPublishPaths, { BulkAction } from '../../../tools/floodbox/bulk-action.js'; +import RequestHandler from '../../../tools/floodbox/request-handler.js'; + +describe('BulkAction', () => { + const org = 'testOrg'; + const repo = 'testRepo'; + const callback = sinon.spy(); + const paths = ['/testOrg/testRepo/content/test1.html', '/testOrg/testRepo/content/test2.html']; + const action = 'publish'; + let daFetchStub; + let delayStub; + + beforeEach(() => { + daFetchStub = sinon.stub(RequestHandler.prototype, 'daFetch'); + delayStub = sinon.stub(BulkAction, 'delay').resolves(); + callback.resetHistory(); + }); + + afterEach(() => { + daFetchStub.restore(); + delayStub.restore(); + }); + + it('should successfully process all paths in batches', async () => { + daFetchStub.resolves({ ok: true, status: 200 }); + + await previewOrPublishPaths({ org, repo, paths, action, callback }); + + sinon.assert.callCount(daFetchStub, paths.length); + sinon.assert.callCount(callback, paths.length); + sinon.assert.calledWithMatch(callback, { statusCode: 200 }); + }); + + it('should handle failed requests and call callback with error', async () => { + daFetchStub.onFirstCall().resolves({ ok: false, status: 500 }); + daFetchStub.onSecondCall().resolves({ ok: true, status: 200 }); + + await previewOrPublishPaths({ org, repo, paths, action, callback }); + + sinon.assert.callCount(daFetchStub, paths.length); + sinon.assert.calledWithMatch(callback, { statusCode: 500, errorMsg: `Failed to ${action}` }); + }); + + it('should clean up paths correctly', async () => { + const cleanedPaths = paths.map((path) => BulkAction.cleanUpPath(path)); + expect(cleanedPaths).to.eql(['/content/test1', '/content/test2']); + }); + + it('should delay between batches', async () => { + daFetchStub.resolves({ ok: true, status: 200 }); + + const longPaths = Array(25).fill('/testOrg/testRepo/content/test1.html'); + await previewOrPublishPaths({ org, repo, paths: longPaths, action, callback }); + + sinon.assert.calledOnce(delayStub); + }); + + it('should handle delete requests', async () => { + daFetchStub.resolves({ ok: true, status: 200 }); + + await previewOrPublishPaths({ + org, repo, paths, action: 'delete', callback, isDelete: true, + }); + + sinon.assert.callCount(daFetchStub, paths.length); + + const args = [ + 'https://admin.hlx.page/delete/testOrg/testRepo/main/content/test1', + { method: 'DELETE' }, + ]; + sinon.assert.calledWithMatch(daFetchStub.firstCall, args[0], args[1]); + }); +}); diff --git a/test/tools/floodbox/floodgate/utils.test.js b/test/tools/floodbox/floodgate/utils.test.js index 616d6e52d3..73f45cac6e 100644 --- a/test/tools/floodbox/floodgate/utils.test.js +++ b/test/tools/floodbox/floodgate/utils.test.js @@ -1,5 +1,5 @@ import { expect } from '@esm-bundle/chai'; -import { validatePaths } from '../../../../tools/floodbox/floodgate/utils.js'; +import { getValidFloodgate, validatePaths } from '../../../../tools/floodbox/floodgate/utils.js'; describe('validatePaths', () => { it('returns valid true with correct org and repo for valid paths', () => { @@ -44,3 +44,19 @@ describe('validatePaths', () => { expect(result).to.eql({ valid: false, org: '', repo: '' }); }); }); + +describe('getValidFloodgate', () => { + it('should return a milo-floodgate element with correct properties', async () => { + // fake DA_SDK response with context and token + const fakeDaSdk = Promise.resolve({ + context: { repo: 'fake-repo' }, + token: 'fake-token', + }); + const cmp = await getValidFloodgate(fakeDaSdk); + + expect(cmp).to.be.instanceOf(HTMLElement); + expect(cmp.tagName.toLowerCase()).to.equal('milo-floodgate'); + expect(cmp.repo).to.equal('fake-repo'); + expect(cmp.token).to.equal('fake-token'); + }); +}); diff --git a/test/tools/floodbox/promote.test.js b/test/tools/floodbox/promote.test.js new file mode 100644 index 0000000000..b22a0d14fa --- /dev/null +++ b/test/tools/floodbox/promote.test.js @@ -0,0 +1,108 @@ +import sinon from 'sinon'; +import promoteFiles from '../../../tools/floodbox/promote.js'; +import RequestHandler from '../../../tools/floodbox/request-handler.js'; + +describe('Promote', () => { + const accessToken = 'testToken'; + const org = 'testOrg'; + const repo = 'testRepo-pink'; + const expName = 'testExp'; + const files = [ + { path: '/testOrg/testRepo-pink/content/test1.html', ext: 'html' }, + { path: '/testOrg/testRepo-pink/content/test2.json', ext: 'json' }, + ]; + const callback = sinon.spy(); + let promoteType = 'floodgate'; + let daFetchStub; + let uploadContentStub; + + beforeEach(() => { + daFetchStub = sinon.stub(RequestHandler.prototype, 'daFetch'); + uploadContentStub = sinon.stub(RequestHandler.prototype, 'uploadContent'); + callback.resetHistory(); + }); + + afterEach(() => { + daFetchStub.restore(); + uploadContentStub.restore(); + }); + + it('should promote files successfully', async () => { + daFetchStub.resolves({ ok: true, text: () => 'file content' }); + uploadContentStub.resolves({ statusCode: 200 }); + + await promoteFiles({ + accessToken, org, repo, promoteType, files, callback, + }); + + sinon.assert.callCount(daFetchStub, files.length); + sinon.assert.callCount(uploadContentStub, files.length); + sinon.assert.callCount(callback, files.length); + sinon.assert.calledWithMatch(callback, { statusCode: 200 }); + }); + + it('should handle fetch errors', async () => { + daFetchStub.resolves({ ok: false, status: 404 }); + + await promoteFiles({ + accessToken, org, repo, promoteType, files, callback, + }); + + sinon.assert.callCount(daFetchStub, files.length); + sinon.assert.callCount(callback, files.length); + sinon.assert.calledWithMatch(callback, { statusCode: 404, errorMsg: 'Failed to fetch' }); + }); + + it('should handle upload errors', async () => { + daFetchStub.resolves({ ok: true, text: () => 'file content' }); + uploadContentStub.resolves({ statusCode: 500, errorMsg: 'Failed to upload file' }); + + await promoteFiles({ + accessToken, org, repo, promoteType, files, callback, + }); + + sinon.assert.callCount(daFetchStub, files.length); + sinon.assert.callCount(uploadContentStub, files.length); + sinon.assert.callCount(callback, files.length); + sinon.assert.calledWithMatch(callback, { statusCode: 500, errorMsg: 'Failed to upload file' }); + }); + + it('should handle blob content', async () => { + const blob = new Blob(['file content'], { type: 'application/octet-stream' }); + daFetchStub.resolves({ ok: true, blob: () => blob }); + uploadContentStub.resolves({ statusCode: 200 }); + + const fileArr = [ + { path: '/testOrg/testRepo-pink/content/image.png', ext: 'png' }, + ]; + await promoteFiles({ + accessToken, org, repo, promoteType, files: fileArr, callback, + }); + + sinon.assert.callCount(daFetchStub, fileArr.length); + sinon.assert.callCount(uploadContentStub, fileArr.length); + sinon.assert.callCount(callback, fileArr.length); + sinon.assert.calledWithMatch(callback, { statusCode: 200 }); + }); + + it('should handle promoteType graybox', async () => { + daFetchStub.resolves({ ok: true, text: () => 'file content' }); + uploadContentStub.resolves({ statusCode: 200 }); + + promoteType = 'graybox'; + const gbFiles = [ + { path: '/testOrg/testRepo-graybox/testExp/content/test1.html', ext: 'html' }, + { path: '/testOrg/testRepo-graybox/testExp/content/test2.json', ext: 'json' }, + ]; + await promoteFiles({ + accessToken, org, repo: 'testRepo-graybox', expName, promoteType, files: gbFiles, callback, + }); + + sinon.assert.callCount(daFetchStub, gbFiles.length); + sinon.assert.callCount(uploadContentStub, gbFiles.length); + sinon.assert.callCount(callback, gbFiles.length); + sinon.assert.calledWithMatch(callback, { statusCode: 200 }); + + sinon.assert.calledWithMatch(uploadContentStub, '/testOrg/testRepo/content/test1.html'); + }); +}); diff --git a/tools/floodbox/bulk-action.js b/tools/floodbox/bulk-action.js index 90bf6e813f..64bb78bcc2 100644 --- a/tools/floodbox/bulk-action.js +++ b/tools/floodbox/bulk-action.js @@ -4,7 +4,7 @@ import RequestHandler from './request-handler.js'; const BATCH_SIZE = 25; const BATCH_DELAY = 2000; -class BulkAction { +export class BulkAction { constructor({ org, repo, callback }) { this.org = org; this.repo = repo; diff --git a/tools/floodbox/floodgate/utils.js b/tools/floodbox/floodgate/utils.js index 84bfeb2a03..367e82a1a9 100644 --- a/tools/floodbox/floodgate/utils.js +++ b/tools/floodbox/floodgate/utils.js @@ -24,8 +24,8 @@ function validatePaths(paths) { return { valid: true, org, repo }; } -async function getValidFloodgate() { - const { context, token } = await DA_SDK; +async function getValidFloodgate(sdk = DA_SDK) { + const { context, token } = await sdk; const cmp = document.createElement('milo-floodgate'); cmp.repo = context.repo; cmp.token = token;