Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions integration-tests/tests/pages/edit.page.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { expect, Page } from '@playwright/test';
import { ReviewPage } from './review.page';
import { prepareTmpDirForSingleUpload, uploadFilesFromTmpDir } from '../utils/file-upload-helpers';

export class EditPage {
constructor(private page: Page) {}

async goto(organism: string, accession: string, version: number) {
await this.page.goto(`/${organism}/submission/edit/${accession}/${version}`);
}

async discardSequenceFile() {
await this.page.getByRole('button', { name: 'Discard file' }).click();
}
Expand Down Expand Up @@ -31,4 +36,14 @@ export class EditPage {
await this.page.waitForURL('**/review', { timeout: 15_000 });
return new ReviewPage(this.page);
}

async uploadExternalFiles(
fileId: string,
fileContents: Record<string, string>,
tmpDir: string,
) {
await prepareTmpDirForSingleUpload(fileContents, tmpDir);
const fileCount = Object.keys(fileContents).length;
await uploadFilesFromTmpDir(this.page, fileId, tmpDir, fileCount);
}
}
15 changes: 14 additions & 1 deletion integration-tests/tests/pages/revision.page.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect, type Page } from '@playwright/test';
import { prepareTmpDirForBulkUpload, uploadFilesFromTmpDir } from '../utils/file-upload-helpers';

/**
* Page object for the sequence revision page.
Expand Down Expand Up @@ -121,7 +122,6 @@ export class RevisionPage {
async submitRevision() {
await this.acceptTerms();
await this.clickSubmit();
await this.clickConfirm();
}

/**
Expand Down Expand Up @@ -187,4 +187,17 @@ export class RevisionPage {

await this.submitRevision();
}

async uploadExternalFiles(
fileId: string,
fileContents: Record<string, Record<string, string>>,
tmpDir: string,
) {
await prepareTmpDirForBulkUpload(fileContents, tmpDir);
const fileCount = Object.values(fileContents).reduce(
(total, files) => total + Object.keys(files).length,
0,
);
await uploadFilesFromTmpDir(this.page, fileId, tmpDir, fileCount);
}
}
64 changes: 9 additions & 55 deletions integration-tests/tests/pages/submission.page.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Page } from '@playwright/test';
import { ReviewPage } from './review.page';
import fs from 'fs';
import path from 'path';
import Papa from 'papaparse';
import { NavigationPage } from './navigation.page';
import { clearTmpDir } from '../utils/tmpdir';
import {
prepareTmpDirForBulkUpload,
prepareTmpDirForSingleUpload,
uploadFilesFromTmpDir,
} from '../utils/file-upload-helpers';

class SubmissionPage {
protected page: Page;
Expand Down Expand Up @@ -68,17 +70,6 @@ class SubmissionPage {
await reviewPage.waitForZeroProcessing();
return reviewPage;
}

protected async _uploadFilesFromTmpDir(testId: string, tmpDir: string, fileCount: number) {
await this.page.getByRole('heading', { name: 'Extra files' }).scrollIntoViewIfNeeded();
// Trigger file upload (don't await) and wait for checkmarks to appear (indicates success)
void this.page.getByTestId(testId).setInputFiles(tmpDir);
return Promise.all(
Array.from({ length: fileCount }, (_, i) =>
this.page.getByText('✓').nth(i).waitFor({ state: 'visible' }),
),
);
}
}

export class SingleSequenceSubmissionPage extends SubmissionPage {
Expand Down Expand Up @@ -140,19 +131,9 @@ export class SingleSequenceSubmissionPage extends SubmissionPage {
fileContents: Record<string, string>,
tmpDir: string,
) {
await this._prepareTmpDirWithFiles(fileContents, tmpDir);
await prepareTmpDirForSingleUpload(fileContents, tmpDir);
const fileCount = Object.keys(fileContents).length;
await this._uploadFilesFromTmpDir(fileId, tmpDir, fileCount);
}

private async _prepareTmpDirWithFiles(fileContents: Record<string, string>, tmpDir: string) {
await clearTmpDir(tmpDir);

await Promise.all(
Object.entries(fileContents).map(([fileName, fileContent]) =>
fs.promises.writeFile(path.join(tmpDir, fileName), fileContent),
),
);
await uploadFilesFromTmpDir(this.page, fileId, tmpDir, fileCount);
}

async completeSubmission(
Expand Down Expand Up @@ -229,43 +210,16 @@ export class BulkSubmissionPage extends SubmissionPage {
});
}

/**
* The given file contents will be stored in a temp dir and then submitted
* for the given file ID.
* @param fileId For which file ID to upload the files.
* @param fileContents A struct: submissionID -> filename -> filecontent.
* @param tmpDir The temporary directory to use for storing files.
*/
async uploadExternalFiles(
fileId: string,
fileContents: Record<string, Record<string, string>>,
tmpDir: string,
) {
await this._prepareTmpDirWithFiles(fileContents, tmpDir);
await prepareTmpDirForBulkUpload(fileContents, tmpDir);
const fileCount = Object.values(fileContents).reduce(
(total, files) => total + Object.keys(files).length,
0,
);
await this._uploadFilesFromTmpDir(fileId, tmpDir, fileCount);
}

private async _prepareTmpDirWithFiles(
fileContents: Record<string, Record<string, string>>,
tmpDir: string,
) {
await clearTmpDir(tmpDir);

// Create submission directories and write files
const submissionIds = Object.keys(fileContents);
await Promise.all(
submissionIds.map((submissionId) => fs.promises.mkdir(path.join(tmpDir, submissionId))),
);
await Promise.all(
Object.entries(fileContents).flatMap(([submissionId, files]) => {
return Object.entries(files).map(([fileName, fileContent]) =>
fs.promises.writeFile(path.join(tmpDir, submissionId, fileName), fileContent),
);
}),
);
await uploadFilesFromTmpDir(this.page, fileId, tmpDir, fileCount);
}
}
119 changes: 119 additions & 0 deletions integration-tests/tests/specs/features/file-sharing.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { expect } from '@playwright/test';
import { test } from '../../fixtures/tmpdir.fixture';
import { EditPage } from '../../pages/edit.page';
import { ReviewPage } from '../../pages/review.page';
import { RevisionPage } from '../../pages/revision.page';
import { SearchPage } from '../../pages/search.page';
import { BulkSubmissionPage, SingleSequenceSubmissionPage } from '../../pages/submission.page';

const ORGANISM_NAME = 'Test organism (with files)';
const ORGANISM_URL_NAME = 'dummy-organism-with-files';
const RAW_READS = 'raw_reads';
const METADATA_HEADERS = ['submissionId', 'country', 'date'];
const COUNTRY_1 = 'Norway';
Expand Down Expand Up @@ -88,3 +94,116 @@ test('bulk submit 1 seq with a 35 MB file', async ({ page, groupId, tmpDir }) =>
const searchPage = await reviewPage.releaseAndGoToReleasedSequences();
await searchPage.checkFileContentInModal('cell', COUNTRY_1, LARGE_FILE);
});

const REVISION_METADATA_HEADERS = ['accession', 'submissionId', 'country', 'date'];
const REVISION_FILES = { 'revised_file.txt': 'This is a revised file.' };
const REVISION_FILES_2 = { 'another_file.txt': 'Another revised file content.' };

test('bulk revise 2 seqs with files', async ({ page, groupId, tmpDir }) => {
test.setTimeout(300_000);

const timestamp = Date.now();
const id1 = `bulk-rev-1-${timestamp}`;
const id2 = `bulk-rev-2-${timestamp}`;
const revId1 = `bulk-rev-updated-1-${timestamp}`;
const revId2 = `bulk-rev-updated-2-${timestamp}`;

// Step 1: Submit and release 2 sequences
const submissionPage = new BulkSubmissionPage(page);
await submissionPage.navigateToSubmissionPage(ORGANISM_NAME);
await submissionPage.uploadMetadataFile(METADATA_HEADERS, [
[id1, COUNTRY_1, '2022-01-01'],
[id2, COUNTRY_2, '2022-01-02'],
]);
const reviewPage = await submissionPage.submitAndWaitForProcessingDone();
const searchPage = await reviewPage.releaseAndGoToReleasedSequences();

// Get the accessions of the released sequences
const accessionVersions = await searchPage.waitForSequencesInSearch(2);
const accession1 = accessionVersions.find((av) => av.version === 1)?.accession;
const accession2 = accessionVersions.find(
(av) => av.version === 1 && av.accession !== accession1,
)?.accession;
expect(accession1).toBeDefined();
expect(accession2).toBeDefined();

// Step 2: Bulk revise with files
const revisionPage = new RevisionPage(page);
await revisionPage.goto(ORGANISM_URL_NAME, groupId);

// Upload revision metadata (with accession column)
const revisionMetadata = [
[accession1, revId1, COUNTRY_1, '2022-02-01'],
[accession2, revId2, COUNTRY_2, '2022-02-02'],
];
await page.getByTestId('metadata_file').setInputFiles({
name: 'revision_metadata.tsv',
mimeType: 'text/plain',
buffer: Buffer.from(
[
REVISION_METADATA_HEADERS.join('\t'),
...revisionMetadata.map((r) => r.join('\t')),
].join('\n'),
),
});

// Upload files for each revision
await revisionPage.uploadExternalFiles(
RAW_READS,
{ [revId1]: REVISION_FILES, [revId2]: REVISION_FILES_2 },
tmpDir,
);
await revisionPage.submitRevision();

// Step 3: Verify in review page and release
const reviewPage2 = new ReviewPage(page);
await reviewPage2.waitForZeroProcessing();
await reviewPage2.releaseValidSequences();

const searchPage2 = new SearchPage(page);
await searchPage2.goToReleasedSequences(ORGANISM_URL_NAME, groupId);
await page.goto(page.url() + '?column_submissionId=true');

// Check that revised sequences have the files
await searchPage2.checkFileContentInModal('cell', revId1, REVISION_FILES);
await searchPage2.checkFileContentInModal('cell', revId2, REVISION_FILES_2);
});

test('single revise seq with files via edit page', async ({ page, groupId, tmpDir }) => {
test.setTimeout(300_000);

// Step 1: Submit and release a sequence
const submissionPage = new SingleSequenceSubmissionPage(page);
await submissionPage.navigateToSubmissionPage(ORGANISM_NAME);
await submissionPage.fillSubmissionFormDummyOrganism({
submissionId: 'single-rev',
country: COUNTRY_1,
date: '2023-01-01',
});
const reviewPage = await submissionPage.submitAndWaitForProcessingDone();
const searchPage = await reviewPage.releaseAndGoToReleasedSequences();

// Step 2: Wait until sequence is available and directly go to revise/edit page
const accessionVersions = await searchPage.waitForSequencesInSearch(1);
const editPage = new EditPage(page);
await editPage.goto(
ORGANISM_URL_NAME,
accessionVersions[0].accession,
accessionVersions[0].version,
);

// Step 3: Upload files in the edit page
await editPage.uploadExternalFiles(RAW_READS, REVISION_FILES, tmpDir);
const reviewPage2 = await editPage.submitChanges();
await reviewPage2.waitForZeroProcessing();
await reviewPage2.releaseValidSequences();

// Step 4: Release and verify files
const searchPage2 = new SearchPage(page);
await searchPage2.goToReleasedSequences(ORGANISM_URL_NAME, groupId);
await searchPage2.checkFileContentInModal(
'link',
`${accessionVersions[0].accession}.${accessionVersions[0].version + 1}`,
REVISION_FILES,
);
});
61 changes: 61 additions & 0 deletions integration-tests/tests/utils/file-upload-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Page } from '@playwright/test';
import fs from 'fs';
import path from 'path';
import { clearTmpDir } from './tmpdir';

/**
* @param fileContents A struct: submissionID -> filename -> filecontent
* @param tmpDir The temporary directory to use for storing files
*/
export async function prepareTmpDirForBulkUpload(
fileContents: Record<string, Record<string, string>>,
tmpDir: string,
) {
await clearTmpDir(tmpDir);

// Create submission directories and write files
const submissionIds = Object.keys(fileContents);
await Promise.all(
submissionIds.map((submissionId) => fs.promises.mkdir(path.join(tmpDir, submissionId))),
);
await Promise.all(
Object.entries(fileContents).flatMap(([submissionId, files]) => {
return Object.entries(files).map(([fileName, fileContent]) =>
fs.promises.writeFile(path.join(tmpDir, submissionId, fileName), fileContent),
);
}),
);
}

/**
* @param fileContents A struct: filename -> filecontent
* @param tmpDir The temporary directory to use for storing files
*/
export async function prepareTmpDirForSingleUpload(
fileContents: Record<string, string>,
tmpDir: string,
) {
await clearTmpDir(tmpDir);

await Promise.all(
Object.entries(fileContents).map(([fileName, fileContent]) =>
fs.promises.writeFile(path.join(tmpDir, fileName), fileContent),
),
);
}

export async function uploadFilesFromTmpDir(
page: Page,
testId: string,
tmpDir: string,
fileCount: number,
) {
await page.getByRole('heading', { name: 'Extra files' }).scrollIntoViewIfNeeded();
// Trigger file upload (don't await) and wait for checkmarks to appear (indicates success)
void page.getByTestId(testId).setInputFiles(tmpDir);
return Promise.all(
Array.from({ length: fileCount }, (_, i) =>
page.getByText('✓').nth(i).waitFor({ state: 'visible' }),
),
);
}
Loading
Loading