| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689 |
- import { expect, vi } from 'vitest'
- import sinon from 'sinon'
- import mongodb from 'mongodb-legacy'
- import Errors from '../../../../app/src/Features/Errors/Errors.js'
- const ObjectId = mongodb.ObjectId
- const MODULE_PATH =
- '../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateHandler.mjs'
- vi.mock('../../../../app/src/Features/Errors/Errors.js', () =>
- vi.importActual('../../../../app/src/Features/Errors/Errors.js')
- )
- describe('TpdsUpdateHandler', function () {
- beforeEach(async function (ctx) {
- ctx.projectName = 'My recipes'
- ctx.userId = new ObjectId()
- ctx.projects = {
- active1: { _id: new ObjectId(), name: ctx.projectName },
- active2: { _id: new ObjectId(), name: ctx.projectName },
- archived1: {
- _id: new ObjectId(),
- name: ctx.projectName,
- archived: [ctx.userId],
- },
- archived2: {
- _id: new ObjectId(),
- name: ctx.projectName,
- archived: [ctx.userId],
- },
- trashed: {
- _id: new ObjectId(),
- name: ctx.projectName,
- trashed: [ctx.userId],
- },
- }
- ctx.source = 'dropbox'
- ctx.path = `/some/file`
- ctx.update = {}
- ctx.folderPath = '/some/folder'
- ctx.folder = {
- _id: new ObjectId(),
- parentFolder_id: new ObjectId(),
- }
- ctx.CooldownManager = {
- isProjectOnCooldown: sinon.stub().resolves(false),
- }
- ctx.FileTypeManager = {
- shouldIgnore: sinon.stub().returns(false),
- }
- ctx.Modules = {
- promises: {
- hooks: { fire: sinon.stub().resolves() },
- },
- }
- ctx.notification = {
- create: sinon.stub().resolves(),
- }
- ctx.NotificationsBuilder = {
- promises: {
- dropboxDuplicateProjectNames: sinon.stub().returns(ctx.notification),
- },
- }
- ctx.ProjectCreationHandler = {
- promises: {
- createBlankProject: sinon.stub().resolves(ctx.projects.active1),
- },
- }
- ctx.ProjectDeleter = {
- promises: {
- markAsDeletedByExternalSource: sinon.stub().resolves(),
- },
- }
- ctx.ProjectGetter = {
- promises: {
- findUsersProjectsByName: sinon.stub(),
- findAllUsersProjects: sinon.stub().resolves({
- owned: Object.values(ctx.projects),
- readAndWrite: [],
- }),
- },
- }
- ctx.ProjectHelper = {
- isArchivedOrTrashed: sinon.stub().returns(false),
- }
- ctx.ProjectHelper.isArchivedOrTrashed
- .withArgs(ctx.projects.archived1, ctx.userId)
- .returns(true)
- ctx.ProjectHelper.isArchivedOrTrashed
- .withArgs(ctx.projects.archived2, ctx.userId)
- .returns(true)
- ctx.ProjectHelper.isArchivedOrTrashed
- .withArgs(ctx.projects.trashed, ctx.userId)
- .returns(true)
- ctx.RootDocManager = {
- setRootDocAutomaticallyInBackground: sinon.stub(),
- }
- ctx.UpdateMerger = {
- promises: {
- deleteUpdate: sinon.stub().resolves(),
- mergeUpdate: sinon.stub().resolves(),
- createFolder: sinon.stub().resolves(ctx.folder),
- },
- }
- vi.doMock('../../../../app/src/Features/Cooldown/CooldownManager', () => ({
- default: ctx.CooldownManager,
- }))
- vi.doMock('../../../../app/src/Features/Uploads/FileTypeManager', () => ({
- default: ctx.FileTypeManager,
- }))
- vi.doMock('../../../../app/src/infrastructure/Modules', () => ({
- default: ctx.Modules,
- }))
- vi.doMock(
- '../../../../app/src/Features/Notifications/NotificationsBuilder',
- () => ({
- default: ctx.NotificationsBuilder,
- })
- )
- vi.doMock(
- '../../../../app/src/Features/Project/ProjectCreationHandler',
- () => ({
- default: ctx.ProjectCreationHandler,
- })
- )
- vi.doMock('../../../../app/src/Features/Project/ProjectDeleter', () => ({
- default: ctx.ProjectDeleter,
- }))
- vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
- default: ctx.ProjectGetter,
- }))
- vi.doMock('../../../../app/src/Features/Project/ProjectHelper', () => ({
- default: ctx.ProjectHelper,
- }))
- vi.doMock(
- '../../../../app/src/Features/Project/ProjectRootDocManager',
- () => ({
- default: ctx.RootDocManager,
- })
- )
- vi.doMock(
- '../../../../app/src/Features/ThirdPartyDataStore/UpdateMerger',
- () => ({
- default: ctx.UpdateMerger,
- })
- )
- ctx.TpdsUpdateHandler = (await import(MODULE_PATH)).default
- })
- describe('getting an update', function () {
- describe('byId', function () {
- describe('with no matching project', function () {
- beforeEach(function (ctx) {
- ctx.projectId = new ObjectId().toString()
- })
- receiveUpdateById()
- expectProjectNotCreated()
- expectUpdateNotProcessed()
- })
- describe('with one matching active project', function () {
- beforeEach(function (ctx) {
- ctx.projectId = ctx.projects.active1._id.toString()
- })
- receiveUpdateById()
- expectProjectNotCreated()
- expectUpdateProcessed()
- })
- describe('with one matching archived project', function () {
- beforeEach(function (ctx) {
- ctx.projectId = ctx.projects.archived1._id.toString()
- })
- receiveUpdateById()
- expectProjectNotCreated()
- expectUpdateNotProcessed()
- })
- describe('with one matching trashed project', function () {
- beforeEach(function (ctx) {
- ctx.projectId = ctx.projects.trashed._id.toString()
- })
- receiveUpdateById()
- expectProjectNotCreated()
- expectUpdateNotProcessed()
- })
- })
- describe('with no matching project', function () {
- setupMatchingProjects([])
- receiveUpdate()
- expectProjectCreated()
- expectUpdateProcessed()
- })
- describe('with one matching active project', function () {
- setupMatchingProjects(['active1'])
- receiveUpdate()
- expectProjectNotCreated()
- expectUpdateProcessed()
- })
- describe('with one matching archived project', function () {
- setupMatchingProjects(['archived1'])
- receiveUpdate()
- expectProjectNotCreated()
- expectUpdateNotProcessed()
- expectDropboxNotUnlinked()
- })
- describe('with two matching active projects', function () {
- setupMatchingProjects(['active1', 'active2'])
- receiveUpdate()
- expectProjectNotCreated()
- expectUpdateNotProcessed()
- expectDropboxUnlinked()
- })
- describe('with two matching archived projects', function () {
- setupMatchingProjects(['archived1', 'archived2'])
- receiveUpdate()
- expectProjectNotCreated()
- expectUpdateNotProcessed()
- expectDropboxNotUnlinked()
- })
- describe('with one matching active and one matching archived project', function () {
- setupMatchingProjects(['active1', 'archived1'])
- receiveUpdate()
- expectProjectNotCreated()
- expectUpdateNotProcessed()
- expectDropboxUnlinked()
- })
- describe('update to a file that should be ignored', async function () {
- setupMatchingProjects(['active1'])
- beforeEach(function (ctx) {
- ctx.FileTypeManager.shouldIgnore.returns(true)
- })
- receiveUpdate()
- expectProjectNotCreated()
- expectUpdateNotProcessed()
- expectDropboxNotUnlinked()
- })
- describe('update to a project on cooldown', async function () {
- setupMatchingProjects(['active1'])
- setupProjectOnCooldown()
- beforeEach(async function (ctx) {
- await expect(
- ctx.TpdsUpdateHandler.promises.newUpdate(
- ctx.userId,
- '', // projectId
- ctx.projectName,
- ctx.path,
- ctx.update,
- ctx.source
- )
- ).to.be.rejectedWith(Errors.TooManyRequestsError)
- })
- expectUpdateNotProcessed()
- })
- })
- describe('getting a file delete', function () {
- describe('byId', function () {
- describe('with no matching project', function () {
- beforeEach(function (ctx) {
- ctx.projectId = new ObjectId().toString()
- })
- receiveFileDeleteById()
- expectDeleteNotProcessed()
- expectProjectNotDeleted()
- })
- describe('with one matching active project', function () {
- beforeEach(function (ctx) {
- ctx.projectId = ctx.projects.active1._id.toString()
- })
- receiveFileDeleteById()
- expectDeleteProcessed()
- expectProjectNotDeleted()
- })
- describe('with one matching archived project', function () {
- beforeEach(function (ctx) {
- ctx.projectId = ctx.projects.archived1._id.toString()
- })
- receiveFileDeleteById()
- expectDeleteNotProcessed()
- expectProjectNotDeleted()
- })
- describe('with one matching trashed project', function () {
- beforeEach(function (ctx) {
- ctx.projectId = ctx.projects.trashed._id.toString()
- })
- receiveFileDeleteById()
- expectDeleteNotProcessed()
- expectProjectNotDeleted()
- })
- })
- describe('with no matching project', function () {
- setupMatchingProjects([])
- receiveFileDelete()
- expectDeleteNotProcessed()
- expectProjectNotDeleted()
- })
- describe('with one matching active project', function () {
- setupMatchingProjects(['active1'])
- receiveFileDelete()
- expectDeleteProcessed()
- expectProjectNotDeleted()
- })
- describe('with one matching archived project', function () {
- setupMatchingProjects(['archived1'])
- receiveFileDelete()
- expectDeleteNotProcessed()
- expectProjectNotDeleted()
- })
- describe('with two matching active projects', function () {
- setupMatchingProjects(['active1', 'active2'])
- receiveFileDelete()
- expectDeleteNotProcessed()
- expectProjectNotDeleted()
- expectDropboxUnlinked()
- })
- describe('with two matching archived projects', function () {
- setupMatchingProjects(['archived1', 'archived2'])
- receiveFileDelete()
- expectDeleteNotProcessed()
- expectProjectNotDeleted()
- expectDropboxNotUnlinked()
- })
- describe('with one matching active and one matching archived project', function () {
- setupMatchingProjects(['active1', 'archived1'])
- receiveFileDelete()
- expectDeleteNotProcessed()
- expectProjectNotDeleted()
- expectDropboxUnlinked()
- })
- })
- describe('getting a project delete', function () {
- describe('with no matching project', function () {
- setupMatchingProjects([])
- receiveProjectDelete()
- expectDeleteNotProcessed()
- expectProjectNotDeleted()
- })
- describe('with one matching active project', function () {
- setupMatchingProjects(['active1'])
- receiveProjectDelete()
- expectDeleteNotProcessed()
- expectProjectDeleted()
- })
- describe('with one matching archived project', function () {
- setupMatchingProjects(['archived1'])
- receiveProjectDelete()
- expectDeleteNotProcessed()
- expectProjectNotDeleted()
- })
- describe('with two matching active projects', function () {
- setupMatchingProjects(['active1', 'active2'])
- receiveProjectDelete()
- expectDeleteNotProcessed()
- expectProjectNotDeleted()
- expectDropboxUnlinked()
- })
- describe('with two matching archived projects', function () {
- setupMatchingProjects(['archived1', 'archived2'])
- receiveProjectDelete()
- expectDeleteNotProcessed()
- expectProjectNotDeleted()
- expectDropboxNotUnlinked()
- })
- describe('with one matching active and one matching archived project', function () {
- setupMatchingProjects(['active1', 'archived1'])
- receiveProjectDelete()
- expectDeleteNotProcessed()
- expectProjectNotDeleted()
- expectDropboxUnlinked()
- })
- })
- describe('getting a folder update', function () {
- describe('with no matching project', function () {
- setupMatchingProjects([])
- receiveFolderUpdate()
- expectProjectCreated()
- expectFolderUpdateProcessed()
- })
- describe('with one matching active project', function () {
- setupMatchingProjects(['active1'])
- receiveFolderUpdate()
- expectProjectNotCreated()
- expectFolderUpdateProcessed()
- })
- describe('with one matching archived project', function () {
- setupMatchingProjects(['archived1'])
- receiveFolderUpdate()
- expectProjectNotCreated()
- expectFolderUpdateNotProcessed()
- expectDropboxNotUnlinked()
- })
- describe('with two matching active projects', function () {
- setupMatchingProjects(['active1', 'active2'])
- receiveFolderUpdate()
- expectProjectNotCreated()
- expectFolderUpdateNotProcessed()
- expectDropboxUnlinked()
- })
- describe('with two matching archived projects', function () {
- setupMatchingProjects(['archived1', 'archived2'])
- receiveFolderUpdate()
- expectProjectNotCreated()
- expectFolderUpdateNotProcessed()
- expectDropboxNotUnlinked()
- })
- describe('with one matching active and one matching archived project', function () {
- setupMatchingProjects(['active1', 'archived1'])
- receiveFolderUpdate()
- expectProjectNotCreated()
- expectFolderUpdateNotProcessed()
- expectDropboxUnlinked()
- })
- describe('update to a project on cooldown', async function () {
- setupMatchingProjects(['active1'])
- setupProjectOnCooldown()
- beforeEach(async function (ctx) {
- await expect(
- ctx.TpdsUpdateHandler.promises.createFolder(
- ctx.userId,
- ctx.projectId,
- ctx.projectName,
- ctx.path
- )
- ).to.be.rejectedWith(Errors.TooManyRequestsError)
- })
- expectFolderUpdateNotProcessed()
- })
- })
- })
- /* Setup helpers */
- function setupMatchingProjects(projectKeys) {
- beforeEach(function (ctx) {
- const projects = projectKeys.map(key => ctx.projects[key])
- ctx.ProjectGetter.promises.findUsersProjectsByName
- .withArgs(ctx.userId, ctx.projectName)
- .resolves(projects)
- })
- }
- function setupProjectOnCooldown() {
- beforeEach(function (ctx) {
- ctx.CooldownManager.isProjectOnCooldown
- .withArgs(ctx.projects.active1._id)
- .resolves(true)
- })
- }
- /* Test helpers */
- function receiveUpdate() {
- beforeEach(async function (ctx) {
- await ctx.TpdsUpdateHandler.promises.newUpdate(
- ctx.userId,
- '', // projectId
- ctx.projectName,
- ctx.path,
- ctx.update,
- ctx.source
- )
- })
- }
- function receiveUpdateById() {
- beforeEach(async function (ctx) {
- await ctx.TpdsUpdateHandler.promises.newUpdate(
- ctx.userId,
- ctx.projectId,
- '', // projectName
- ctx.path,
- ctx.update,
- ctx.source
- )
- })
- }
- function receiveFileDelete() {
- beforeEach(async function (ctx) {
- await ctx.TpdsUpdateHandler.promises.deleteUpdate(
- ctx.userId,
- '', // projectId
- ctx.projectName,
- ctx.path,
- ctx.source
- )
- })
- }
- function receiveFileDeleteById() {
- beforeEach(async function (ctx) {
- await ctx.TpdsUpdateHandler.promises.deleteUpdate(
- ctx.userId,
- ctx.projectId,
- '', // projectName
- ctx.path,
- ctx.source
- )
- })
- }
- function receiveProjectDelete() {
- beforeEach(async function (ctx) {
- await ctx.TpdsUpdateHandler.promises.deleteUpdate(
- ctx.userId,
- '', // projectId
- ctx.projectName,
- '/',
- ctx.source
- )
- })
- }
- function receiveFolderUpdate() {
- beforeEach(async function (ctx) {
- await ctx.TpdsUpdateHandler.promises.createFolder(
- ctx.userId,
- ctx.projectId,
- ctx.projectName,
- ctx.folderPath
- )
- })
- }
- /* Expectations */
- function expectProjectCreated() {
- it('creates a project', function (ctx) {
- expect(
- ctx.ProjectCreationHandler.promises.createBlankProject
- ).to.have.been.calledWith(ctx.userId, ctx.projectName)
- })
- it('sets the root doc', function (ctx) {
- expect(
- ctx.RootDocManager.setRootDocAutomaticallyInBackground
- ).to.have.been.calledWith(ctx.projects.active1._id)
- })
- }
- function expectProjectNotCreated() {
- it('does not create a project', function (ctx) {
- expect(ctx.ProjectCreationHandler.promises.createBlankProject).not.to.have
- .been.called
- })
- it('does not set the root doc', function (ctx) {
- expect(ctx.RootDocManager.setRootDocAutomaticallyInBackground).not.to.have
- .been.called
- })
- }
- function expectUpdateProcessed() {
- it('processes the update', function (ctx) {
- expect(ctx.UpdateMerger.promises.mergeUpdate).to.have.been.calledWith(
- ctx.userId,
- ctx.projects.active1._id,
- ctx.path,
- ctx.update,
- ctx.source
- )
- })
- }
- function expectUpdateNotProcessed() {
- it('does not process the update', function (ctx) {
- expect(ctx.UpdateMerger.promises.mergeUpdate).not.to.have.been.called
- })
- }
- function expectFolderUpdateProcessed() {
- it('processes the folder update', function (ctx) {
- expect(ctx.UpdateMerger.promises.createFolder).to.have.been.calledWith(
- ctx.projects.active1._id,
- ctx.folderPath,
- ctx.userId
- )
- })
- }
- function expectFolderUpdateNotProcessed() {
- it("doesn't process the folder update", function (ctx) {
- expect(ctx.UpdateMerger.promises.createFolder).not.to.have.been.called
- })
- }
- function expectDropboxUnlinked() {
- it('unlinks Dropbox', function (ctx) {
- expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith(
- 'removeDropbox',
- ctx.userId,
- 'duplicate-projects'
- )
- })
- it('creates a notification that dropbox was unlinked', function (ctx) {
- expect(
- ctx.NotificationsBuilder.promises.dropboxDuplicateProjectNames
- ).to.have.been.calledWith(ctx.userId)
- expect(ctx.notification.create).to.have.been.calledWith(ctx.projectName)
- })
- }
- function expectDropboxNotUnlinked() {
- it('does not unlink Dropbox', function (ctx) {
- expect(ctx.Modules.promises.hooks.fire).not.to.have.been.called
- })
- it('does not create a notification that dropbox was unlinked', function (ctx) {
- expect(ctx.NotificationsBuilder.promises.dropboxDuplicateProjectNames).not
- .to.have.been.called
- })
- }
- function expectDeleteProcessed() {
- it('processes the delete', function (ctx) {
- expect(ctx.UpdateMerger.promises.deleteUpdate).to.have.been.calledWith(
- ctx.userId,
- ctx.projects.active1._id,
- ctx.path,
- ctx.source
- )
- })
- }
- function expectDeleteNotProcessed() {
- it('does not process the delete', function (ctx) {
- expect(ctx.UpdateMerger.promises.deleteUpdate).not.to.have.been.called
- })
- }
- function expectProjectDeleted() {
- it('deletes the project', function (ctx) {
- expect(
- ctx.ProjectDeleter.promises.markAsDeletedByExternalSource
- ).to.have.been.calledWith(ctx.projects.active1._id)
- })
- }
- function expectProjectNotDeleted() {
- it('does not delete the project', function (ctx) {
- expect(ctx.ProjectDeleter.promises.markAsDeletedByExternalSource).not.to
- .have.been.called
- })
- }
|