add unit tests

This commit is contained in:
Mark Kim
2023-11-09 09:37:20 -05:00
parent ee759216bc
commit 0f10fada1b
3 changed files with 639 additions and 65 deletions

View File

@@ -0,0 +1,426 @@
import { database as db } from '../../../common/database';
import * as models from '../../../models';
import { Project } from '../../../models/project';
import { Request } from '../../../models/request';
import { RequestGroup } from '../../../models/request-group';
import { Workspace } from '../../../models/workspace';
import { WorkspaceMeta } from '../../../models/workspace-meta';
type MockModel = Partial<Project | Workspace | RequestGroup | Request | WorkspaceMeta>;
const mocksWithoutParentIdProjects: Record<string, MockModel[]> = {
[models.project.type]: [
{
_id: 'proj_1',
name: 'Proj 1',
parentId: null,
remoteId: null,
} as unknown as Project,
{
_id: 'proj_2',
name: 'Proj 2',
parentId: null,
remoteId: null,
} as unknown as Project,
{
_id: 'proj_3',
name: 'Proj 3',
parentId: null,
remoteId: null,
} as unknown as Project,
],
[models.workspace.type]: [
{
_id: 'wrk_1',
name: 'Wrk 1',
parentId: null,
} as unknown as Workspace,
{
_id: 'wrk_2',
name: 'Wrk 2',
parentId: 'proj_3',
},
{
_id: 'wrk_3',
name: 'Wrk 3',
parentId: 'proj_3',
},
{
_id: 'wrk_4',
name: 'Wrk 4',
parentId: 'proj_3',
},
],
[models.requestGroup.type]: [
{
_id: 'fld_1',
parentId: 'wrk_1',
name: 'Fld 1',
},
{
_id: 'fld_2',
parentId: 'wrk_1',
name: 'Fld 2',
},
{
_id: 'fld_3',
parentId: 'fld_1',
name: 'Fld 3',
},
],
[models.request.type]: [
{
_id: 'req_1',
parentId: 'fld_1',
name: 'Req 1',
},
{
_id: 'req_2',
parentId: 'fld_1',
name: 'Req 2',
},
{
_id: 'req_3',
parentId: 'wrk_1',
name: 'Req 3',
},
{
_id: 'req_4',
parentId: 'fld_3',
name: 'Req 4',
},
{
_id: 'req_5',
parentId: 'wrk_1',
name: 'Req 5',
},
],
};
const mocksHiddenWorkspaces: Record<string, MockModel[]> = {
[models.project.type]: [
{
_id: 'proj_1',
name: 'Proj 1',
parentId: 'org_1',
remoteId: 'team_1',
},
],
[models.workspace.type]: [
{
_id: 'wrk_1',
name: 'Wrk 1',
parentId: 'proj_1',
},
{
_id: 'wrk_2',
name: 'Wrk 2',
parentId: null,
} as unknown as Workspace,
{
_id: 'wrk_3',
name: 'Wrk 3',
parentId: null,
} as unknown as Workspace,
{
_id: 'wrk_4',
name: 'Wrk 4',
parentId: null,
} as unknown as Workspace,
],
[models.requestGroup.type]: [
{
_id: 'fld_1',
parentId: 'wrk_1',
name: 'Fld 1',
},
{
_id: 'fld_2',
parentId: 'wrk_1',
name: 'Fld 2',
},
{
_id: 'fld_3',
parentId: 'fld_1',
name: 'Fld 3',
},
],
[models.request.type]: [
{
_id: 'req_1',
parentId: 'fld_1',
name: 'Req 1',
},
{
_id: 'req_2',
parentId: 'fld_1',
name: 'Req 2',
},
{
_id: 'req_3',
parentId: 'wrk_1',
name: 'Req 3',
},
{
_id: 'req_4',
parentId: 'fld_3',
name: 'Req 4',
},
{
_id: 'req_5',
parentId: 'wrk_1',
name: 'Req 5',
},
],
};
const mocksNoProblem: Record<string, MockModel[]> = {
[models.project.type]: [
{
_id: 'proj_1',
name: 'Proj 1',
parentId: 'org_1',
remoteId: null,
},
{
_id: 'proj_2',
name: 'Proj 2',
parentId: 'org_2',
remoteId: null,
},
{
_id: 'proj_3',
name: 'Proj 3',
parentId: 'org_3',
remoteId: 'team_proj_3',
},
],
[models.workspace.type]: [
{
_id: 'wrk_1',
name: 'Wrk 1',
parentId: 'proj_1',
},
{
_id: 'wrk_2',
name: 'Wrk 2',
parentId: 'proj_3',
},
],
[models.requestGroup.type]: [
{
_id: 'fld_1',
parentId: 'wrk_1',
name: 'Fld 1',
},
{
_id: 'fld_2',
parentId: 'wrk_1',
name: 'Fld 2',
},
{
_id: 'fld_3',
parentId: 'fld_1',
name: 'Fld 3',
},
],
[models.request.type]: [
{
_id: 'req_1',
parentId: 'fld_1',
name: 'Req 1',
},
{
_id: 'req_2',
parentId: 'fld_1',
name: 'Req 2',
},
{
_id: 'req_3',
parentId: 'wrk_1',
name: 'Req 3',
},
{
_id: 'req_4',
parentId: 'fld_3',
name: 'Req 4',
},
{
_id: 'req_5',
parentId: 'wrk_1',
name: 'Req 5',
},
],
};
const mocksToBeRepaired: Record<string, MockModel[]> = {
[models.project.type]: [
{
_id: 'proj_1',
name: 'Proj 1',
parentId: null,
remoteId: 'team_1',
} as unknown as Project,
{
_id: 'proj_2',
name: 'Proj 2',
parentId: null,
remoteId: 'team_2',
} as unknown as Project,
{
_id: 'proj_3',
name: 'Proj 3',
parentId: null,
remoteId: null,
} as unknown as Project,
],
[models.workspace.type]: [
{
_id: 'wrk_1',
name: 'Wrk 1',
parentId: null,
} as unknown as Workspace,
{
_id: 'wrk_2',
name: 'Wrk 2',
parentId: 'proj_3',
},
{
_id: 'wrk_3',
name: 'Wrk 3',
parentId: 'proj_3',
},
{
_id: 'wrk_4',
name: 'Wrk 4',
parentId: 'proj_3',
},
],
[models.requestGroup.type]: [
{
_id: 'fld_1',
parentId: 'wrk_1',
name: 'Fld 1',
},
{
_id: 'fld_2',
parentId: 'wrk_1',
name: 'Fld 2',
},
{
_id: 'fld_3',
parentId: 'fld_1',
name: 'Fld 3',
},
],
[models.request.type]: [
{
_id: 'req_1',
parentId: 'fld_1',
name: 'Req 1',
},
{
_id: 'req_2',
parentId: 'fld_1',
name: 'Req 2',
},
{
_id: 'req_3',
parentId: 'wrk_1',
name: 'Req 3',
},
{
_id: 'req_4',
parentId: 'fld_3',
name: 'Req 4',
},
{
_id: 'req_5',
parentId: 'wrk_1',
name: 'Req 5',
},
],
};
const mockRemoteBackgroundCheck = {
myWorkspaceId: 'org_my',
remoteFileSnapshot: {
'org_my': {
ownedByMe: true,
isPersonal: true,
fileIdMap: {
'wrk_1': 'team_1',
'wrk_2': 'team_1',
'wrk_3': 'team_2',
'wrk_4': 'team_3',
},
projectIds: ['team_1', 'team_2', 'team_3', 'team_4', 'team_5'],
},
'org_1': {
ownedByMe: false,
isPersonal: false,
fileIdMap: {},
projectIds: [],
},
},
byRemoteProjectId: new Map([
['team_1', 'org_my'],
['team_2', 'org_my'],
['team_3', 'org_my'],
['team_4', 'org_my'],
['team_5', 'org_my'],
['team_6', 'org_1'],
]),
byRemoteFileId: new Map([
['wrk_1', {
remoteOrgId: 'org_my',
remoteProjectId: 'team_1',
}],
['wrk_2', {
remoteOrgId: 'org_my',
remoteProjectId: 'team_1',
}],
['wrk_3', {
remoteOrgId: 'org_my',
remoteProjectId: 'team_2',
}],
['wrk_4', {
remoteOrgId: 'org_my',
remoteProjectId: 'team_2',
}],
]),
};
const mockRemoteBackgroundCheckMessedUp = {
myWorkspaceId: 'org_my',
remoteFileSnapshot: {
'org_my': {
ownedByMe: true,
isPersonal: true,
fileIdMap: {
},
projectIds: [],
},
'org_1': {
ownedByMe: false,
isPersonal: false,
fileIdMap: {},
projectIds: [],
},
},
byRemoteProjectId: new Map(),
byRemoteFileId: new Map(),
};
export function fakeDatabase(data: Record<string, MockModel[]>) {
const promises: Promise<models.BaseModel>[] = [];
for (const type of Object.keys(data)) {
for (const doc of data[type]) {
console.log(doc);
// @ts-expect-error -- TSCONVERSION
promises.push(db.insert<models.BaseModel>({ ...doc, type }));
}
}
return Promise.all(promises);
}
export { mocksWithoutParentIdProjects, mocksHiddenWorkspaces, mocksNoProblem, mocksToBeRepaired };
export { mockRemoteBackgroundCheck, mockRemoteBackgroundCheckMessedUp };

View File

@@ -1,73 +1,212 @@
import { afterAll, beforeEach, describe, expect, it, jest } from '@jest/globals';
import { beforeEach, describe, expect, it } from '@jest/globals';
import { globalBeforeEach } from '../../../__jest__/before-each';
import * as models from '../../../models';
import MemoryDriver from '../../store/drivers/memory-driver';
import { pushSnapshotOnInitialize } from '../initialize-backend-project';
import { VCS } from '../vcs';
import { _migrateToCloudSync, _migrateToLocalVault, _validateProjectsWithRemote, scanForMigration, shouldMigrate } from '../migrate-projects-into-organization';
import { fakeDatabase, mockRemoteBackgroundCheck, mockRemoteBackgroundCheckMessedUp, mocksHiddenWorkspaces, mocksNoProblem, mocksToBeRepaired, mocksWithoutParentIdProjects } from './database.mock';
describe('migrate-projects-into-organizatino', () => {
beforeEach(globalBeforeEach);
console.log({
_migrateToCloudSync, _migrateToLocalVault, _validateProjectsWithRemote,
});
describe('scanForMigration()', () => {
beforeEach(async () => {
await globalBeforeEach();
});
it('should scan all the untracked projects and files (both files under projects and un parent project files)', async () => {
await fakeDatabase(mocksWithoutParentIdProjects);
describe('pushSnapshotOnInitialize()', () => {
const vcs = new VCS(new MemoryDriver());
const result = await scanForMigration();
console.log(result.filesByProject.size);
expect(result.filesByProject.size).toBe(3);
const pushSpy = jest.spyOn(vcs, 'push');
expect(result.filesByProject.has('wrk_2'));
expect(result.filesByProject.has('wrk_3'));
expect(result.filesByProject.has('wrk_4'));
expect(result.filesNoProject.size).toBe(1);
expect(result.filesNoProject.has('wrk_1'));
expect(result.projects.size).toBe(3);
expect(result.projects.has('proj_1'));
expect(result.projects.has('proj_2'));
expect(result.projects.has('proj_3'));
});
beforeEach(() => {
jest.resetAllMocks();
});
it('should scan all the untracked files (only files)', async () => {
await fakeDatabase(mocksHiddenWorkspaces);
afterAll(() => {
pushSpy.mockClear();
});
const result = await scanForMigration();
expect(result.filesByProject.size).toBe(0);
expect(result.filesNoProject.size).toBe(3);
expect(result.filesNoProject.has('wrk_2'));
expect(result.filesNoProject.has('wrk_3'));
expect(result.filesNoProject.has('wrk_4'));
expect(result.projects.size).toBe(0);
});
it('should not push if no active project', async () => {
const project = await models.project.create({ remoteId: null });
const workspace = await models.workspace.create({ parentId: project._id });
const workspaceMeta = await models.workspaceMeta.create({ parentId: workspace._id });
vcs.clearBackendProject();
it('should scan when there are no untracked projects and files', async () => {
await fakeDatabase(mocksNoProblem);
await pushSnapshotOnInitialize({ vcs, project, workspace });
expect(pushSpy).not.toHaveBeenCalled();
await expect(models.workspaceMeta.getByParentId(workspace._id)).resolves.toStrictEqual(workspaceMeta);
});
it('should not push snapshot if not remote project', async () => {
const project = await models.project.create({ remoteId: null });
const workspace = await models.workspace.create({ parentId: project._id });
const workspaceMeta = await models.workspaceMeta.create({ parentId: workspace._id });
vcs.switchAndCreateBackendProjectIfNotExist(workspace._id, workspace.name);
await pushSnapshotOnInitialize({ vcs, project, workspace });
expect(pushSpy).not.toHaveBeenCalled();
await expect(models.workspaceMeta.getByParentId(workspace._id)).resolves.toStrictEqual(workspaceMeta);
});
it('should not push snapshot if workspace not in project', async () => {
const project = await models.project.create({ remoteId: 'abc' });
const anotherProject = await models.project.create({ remoteId: 'def' });
const workspace = await models.workspace.create({ parentId: anotherProject._id });
vcs.switchAndCreateBackendProjectIfNotExist(workspace._id, workspace.name);
await pushSnapshotOnInitialize({ vcs, project, workspace });
expect(pushSpy).not.toHaveBeenCalled();
});
it('should push snapshot if conditions are met', async () => {
const project = await models.project.create({ remoteId: 'abc', parentId: 'team_abc' });
const workspace = await models.workspace.create({ parentId: project._id });
await models.workspaceMeta.create({ parentId: workspace._id, pushSnapshotOnInitialize: true });
vcs.switchAndCreateBackendProjectIfNotExist(workspace._id, workspace.name);
await pushSnapshotOnInitialize({ vcs, project, workspace });
expect(pushSpy).toHaveBeenCalledWith({ teamId: 'team_abc', teamProjectId: project.remoteId });
const updatedMeta = await models.workspaceMeta.getByParentId(workspace._id);
expect(updatedMeta?.pushSnapshotOnInitialize).toBe(false);
});
const result = await scanForMigration();
expect(result.filesByProject.size).toBe(0);
expect(result.filesNoProject.size).toBe(0);
expect(result.projects.size).toBe(0);
});
});
describe('shouldMigrate()', () => {
beforeEach(async () => {
await globalBeforeEach();
});
it('should return true with untracked projects and files (both files under projects and un parent project files)', async () => {
await fakeDatabase(mocksWithoutParentIdProjects);
const queue = await scanForMigration();
const result = await shouldMigrate(queue);
expect(result).toBeTruthy();
});
it('should return true with untracked files (only files)', async () => {
await fakeDatabase(mocksHiddenWorkspaces);
const queue = await scanForMigration();
const result = await shouldMigrate(queue);
expect(result).toBeTruthy();
});
it('should return false with no untracked files', async () => {
await fakeDatabase(mocksNoProblem);
const queue = await scanForMigration();
const result = await shouldMigrate(queue);
expect(result).not.toBeTruthy();
});
});
describe('_migrateToCloudSync()', () => {
beforeEach(async () => {
await globalBeforeEach();
});
it('should fallback to local migration when there are no valid remote projects', async () => {
await fakeDatabase(mocksWithoutParentIdProjects);
const queue = await scanForMigration();
const records = await _migrateToCloudSync(queue, mockRemoteBackgroundCheckMessedUp);
expect(records.filesForSync.length).toBe(0);
expect(records.projects.size).toBe(3);
expect(records.projects.has('proj_1')).toBeTruthy();
expect(records.projects.has('proj_2')).toBeTruthy();
expect(records.projects.has('proj_3')).toBeTruthy();
const [proj1, proj2, proj3] = await Promise.all([
models.project.getById(records.projects.get('proj_1')!),
models.project.getById(records.projects.get('proj_2')!),
models.project.getById(records.projects.get('proj_3')!),
]);
expect(proj1).toBeTruthy();
expect(proj1!.remoteId).toBeNull();
expect(proj2).toBeTruthy();
expect(proj2!.remoteId).toBeNull();
expect(proj3).toBeTruthy();
expect(proj3!.remoteId).toBeNull();
expect(records.files.size).toBe(4);
expect(records.files.has('wrk_1')).toBeTruthy();
expect(records.files.has('wrk_2')).toBeTruthy(); // part of proj_3
expect(records.files.has('wrk_3')).toBeTruthy(); // part of proj_3
expect(records.files.has('wrk_4')).toBeTruthy(); // part of proj_3
});
it('should repair projects with valid remote ids with personal workspace id if not linked correctly', async () => {
await fakeDatabase(mocksToBeRepaired);
const beforeProj1 = await models.project.getById('proj_1');
expect(beforeProj1!.parentId).toBeNull();
const beforeProj2 = await models.project.getById('proj_2');
expect(beforeProj2!.parentId).toBeNull();
const queue = await scanForMigration();
const records = await _migrateToCloudSync(queue, mockRemoteBackgroundCheck);
expect(records.projects.size).toBe(1);
expect(records.files.size).toBe(4); // => we've duplicated the files because they are not in correct linking between the local and remote
const repairedProj1 = await models.project.getById('proj_1');
expect(repairedProj1!.parentId).toBe('org_my');
const repairedProj2 = await models.project.getById('proj_2');
expect(repairedProj2!.parentId).toBe('org_my');
const [wrk1, wrk2, wrk3, wrk4] = await Promise.all([
models.workspace.getById(records.files.get('wrk_1')!),
models.workspace.getById(records.files.get('wrk_2')!),
models.workspace.getById(records.files.get('wrk_3')!),
models.workspace.getById(records.files.get('wrk_4')!),
]);
expect(wrk1!.parentId).not.toBeNull(); // files without parent id (no project linking) => now corrected
expect(wrk2!.parentId).not.toBe('proj_3'); // files with the parent id that does not exist in the cloud => duplicated and linked to the existing remote project
expect(wrk3!.parentId).not.toBe('proj_3'); // files with the parent id that does not exist in the cloud => duplicated and linked to the existing remote project
expect(wrk4!.parentId).not.toBe('proj_3'); // files with the parent id that does not exist in the cloud => duplicated and linked to the existing remote project
// these duplicated 4 files need to be synced now
expect(records.filesForSync.length).toBe(4);
});
});
describe('_migrateToLocalVault()', () => {
beforeEach(async () => {
await globalBeforeEach();
});
it('should duplicate all files and projects', async () => {
await fakeDatabase(mocksWithoutParentIdProjects);
const queue = await scanForMigration();
const records = await _migrateToLocalVault(queue, mockRemoteBackgroundCheck);
// should never happen
expect(records).not.toHaveProperty('filesForSync');
expect(records.projects.size).toBe(3);
expect(records.projects.has('proj_1')).toBeTruthy();
expect(records.projects.has('proj_2')).toBeTruthy();
expect(records.projects.has('proj_3')).toBeTruthy();
expect(records.files.size).toBe(4);
expect(records.files.has('wrk_1')).toBeTruthy();
expect(records.files.has('wrk_2')).toBeTruthy(); // part of proj_3
expect(records.files.has('wrk_3')).toBeTruthy(); // part of proj_3
expect(records.files.has('wrk_4')).toBeTruthy(); // part of proj_3
});
it('should link untracked files to a newly created local vault', async () => {
await fakeDatabase(mocksHiddenWorkspaces);
const queue = await scanForMigration();
const records = await _migrateToLocalVault(queue, mockRemoteBackgroundCheck);
expect(records).not.toHaveProperty('filesForSync');
expect(records.projects.size).toBe(0);
expect(records.projects.has('proj_1')).not.toBeTruthy();
expect(records.files.size).toBe(3);
expect(records.files.has('wrk_1')).not.toBeTruthy(); // part of proj_1
expect(records.files.has('wrk_2')).toBeTruthy();
expect(records.files.has('wrk_3')).toBeTruthy();
expect(records.files.has('wrk_4')).toBeTruthy();
const [wrk1, wrk2, wrk3, wrk4] = await Promise.all([
models.workspace.getById('wrk_1'),
models.workspace.getById(records.files.get('wrk_2')),
models.workspace.getById(records.files.get('wrk_3')),
models.workspace.getById(records.files.get('wrk_4')),
]);
expect(wrk1).toMatchObject({});
expect(wrk2).toBeTruthy();
expect(wrk2?.parentId).not.toBeNull();
const { parentId: newVaultId } = wrk2!;
expect(wrk3).toBeTruthy();
expect(wrk3?.parentId).toEqual(newVaultId);
expect(wrk4).toBeTruthy();
expect(wrk4?.parentId).toEqual(newVaultId);
});
});

View File

@@ -49,6 +49,8 @@ export const scanForMigration = async (): Promise<QueueForMigration> => {
parentId: null,
});
console.log({ legacyRemoteProjects });
for (const project of legacyRemoteProjects) {
console.log('[migration] found legacy remote project', project);
queueLocalProjects.add(project._id);
@@ -68,6 +70,7 @@ export const scanForMigration = async (): Promise<QueueForMigration> => {
_id: { $ne: models.project.SCRATCHPAD_PROJECT_ID },
});
console.log({ localProjects });
for (const project of localProjects) {
console.log('[migration] found local project', project._id);
queueLocalProjects.add(project._id);
@@ -85,6 +88,8 @@ export const scanForMigration = async (): Promise<QueueForMigration> => {
parentId: null,
});
console.log({ untrackedFiles });
for (const file of untrackedFiles) {
console.log('[migration] found an untracked file with no parents', file._id);
queueLocalFilesNoProject.add(file._id);
@@ -107,7 +112,7 @@ interface RemoteBackground {
remoteProjectId: RemoteProjectId;
}>;
}
const remoteBackgroundCheck = async (sessionId: string): Promise<RemoteBackground | null> => {
export const remoteBackgroundCheck = async (sessionId: string): Promise<RemoteBackground | null> => {
const response = await window.main.insomniaFetch<RemoteFileSnapshot | { error: string; message: string }>({
method: 'GET',
path: '/v1/user/file-snapshot',
@@ -171,7 +176,8 @@ interface RecordsForMigration {
projects: Map<string, string>;
files: Map<string, string>;
};
const _migrateToLocalVault = async (queue: QueueForMigration, remoteBackground: RemoteBackground): Promise<RecordsForMigration> => {
/** @private */
export const _migrateToLocalVault = async (queue: QueueForMigration, remoteBackground: RemoteBackground): Promise<RecordsForMigration> => {
const recordsForProjectMigration = new Map<string, string>();
const recordsForFileMigration = new Map<string, string>();
@@ -232,7 +238,8 @@ const _migrateToLocalVault = async (queue: QueueForMigration, remoteBackground:
};
};
const _validateProjectsWithRemote = async (queue: QueueForMigration, remoteBackground: RemoteBackground) => {
/** @private */
export const _validateProjectsWithRemote = async (queue: QueueForMigration, remoteBackground: RemoteBackground) => {
console.log('[migration] validating projects against the remote');
const { myWorkspaceId } = remoteBackground;
const myWorkspace = remoteBackground.remoteFileSnapshot[myWorkspaceId];
@@ -303,7 +310,8 @@ const _validateProjectsWithRemote = async (queue: QueueForMigration, remoteBackg
type RecordsForCloudMigration = RecordsForMigration & {
filesForSync: Workspace[];
};
const _migrateToCloudSync = async (
/** @private */
export const _migrateToCloudSync = async (
queue: QueueForMigration,
remoteBackground: RemoteBackground,
): Promise<RecordsForCloudMigration> => {
@@ -330,6 +338,7 @@ const _migrateToCloudSync = async (
for (const validProject of validProjects) {
if (validProject.parentId !== myWorkspaceId) {
console.log('[migration] repairing team-project to organization linking for local database');
// TODO: update the array validProjects here
await database.docUpdate<Project>(validProject, {
parentId: myWorkspaceId,
});