@opencloning/ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/package.json +37 -0
- package/src/components/AppAlerts.jsx +19 -0
- package/src/components/CloningHistory.jsx +40 -0
- package/src/components/DataModelDisplayer.jsx +30 -0
- package/src/components/DescriptionEditor.jsx +68 -0
- package/src/components/DownloadCloningStrategyDialog.jsx +84 -0
- package/src/components/DownloadSequenceFileDialog.jsx +90 -0
- package/src/components/DragAndDropCloningHistoryWrapper.jsx +39 -0
- package/src/components/DraggableDialogPaper.jsx +16 -0
- package/src/components/EditSequenceNameDialog.jsx +90 -0
- package/src/components/ExternalServicesStatusCheck.jsx +92 -0
- package/src/components/HistoryLoadedDialog.jsx +49 -0
- package/src/components/LoadCloningHistoryWrapper.jsx +166 -0
- package/src/components/MainSequenceCheckBox.jsx +83 -0
- package/src/components/MainSequenceEditor.jsx +165 -0
- package/src/components/NetworkNode.jsx +159 -0
- package/src/components/NetworkTree.css +127 -0
- package/src/components/ObjectTable.jsx +24 -0
- package/src/components/OpenCloning.jsx +102 -0
- package/src/components/OverhangsDisplay.jsx +25 -0
- package/src/components/SequenceEditor.jsx +120 -0
- package/src/components/SequenceTab.jsx +14 -0
- package/src/components/TemplateSequence.jsx +38 -0
- package/src/components/annotation/PlannotateAnnotationReport.jsx +33 -0
- package/src/components/annotation/useUpdateAnnotationInMainSequence.js +39 -0
- package/src/components/assembler/AssemblePartWidget.jsx +252 -0
- package/src/components/assembler/Assembler.jsx +273 -0
- package/src/components/assembler/AssemblerPart.jsx +99 -0
- package/src/components/assembler/StopIcon.jsx +34 -0
- package/src/components/assembler/assembler_data2.json +50 -0
- package/src/components/assembler/assembly_component.module.css +81 -0
- package/src/components/assembler/moclo.json +110 -0
- package/src/components/assembler/sbol_visual_glyphs/LICENSE.html +21 -0
- package/src/components/assembler/sbol_visual_glyphs/assembly-scar.svg +63 -0
- package/src/components/assembler/sbol_visual_glyphs/cds-stop.svg +85 -0
- package/src/components/assembler/sbol_visual_glyphs/cds.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/chromosomal-locus.svg +78 -0
- package/src/components/assembler/sbol_visual_glyphs/engineered-region.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/five-prime-sticky-restriction-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/origin-of-replication.svg +57 -0
- package/src/components/assembler/sbol_visual_glyphs/primer-binding-site.svg +59 -0
- package/src/components/assembler/sbol_visual_glyphs/promoter.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/ribosome-entry-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/specific-recombination-site.svg +59 -0
- package/src/components/assembler/sbol_visual_glyphs/terminator.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/three-prime-sticky-restriction-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs.js +36 -0
- package/src/components/assembler/useAssembler.js +71 -0
- package/src/components/dummy/DummyInterface.js +41 -0
- package/src/components/dummy/GetSequenceFileAndDatabaseIdComponent.jsx +59 -0
- package/src/components/eLabFTW/ELabFTWCategorySelect.cy.jsx +86 -0
- package/src/components/eLabFTW/ELabFTWCategorySelect.jsx +43 -0
- package/src/components/eLabFTW/ELabFTWFileSelect.cy.jsx +43 -0
- package/src/components/eLabFTW/ELabFTWFileSelect.jsx +29 -0
- package/src/components/eLabFTW/ELabFTWResourceSelect.cy.jsx +107 -0
- package/src/components/eLabFTW/ELabFTWResourceSelect.jsx +23 -0
- package/src/components/eLabFTW/GetPrimerComponent.cy.jsx +261 -0
- package/src/components/eLabFTW/GetPrimerComponent.jsx +55 -0
- package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.cy.jsx +184 -0
- package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.jsx +62 -0
- package/src/components/eLabFTW/LoadHistoryComponent.cy.jsx +235 -0
- package/src/components/eLabFTW/LoadHistoryComponent.jsx +51 -0
- package/src/components/eLabFTW/PrimersNotInDatabaseComponent.cy.jsx +159 -0
- package/src/components/eLabFTW/PrimersNotInDatabaseComponent.jsx +54 -0
- package/src/components/eLabFTW/SubmitToDatabaseComponent.cy.jsx +185 -0
- package/src/components/eLabFTW/SubmitToDatabaseComponent.jsx +51 -0
- package/src/components/eLabFTW/common.js +26 -0
- package/src/components/eLabFTW/eLabFTWInterface.js +294 -0
- package/src/components/eLabFTW/eLabFTWInterface.test.js +839 -0
- package/src/components/eLabFTW/envValues.js +7 -0
- package/src/components/eLabFTW/utils.js +39 -0
- package/src/components/form/CustomFormHelperText.jsx +10 -0
- package/src/components/form/EnzymeMultiSelect.cy.jsx +61 -0
- package/src/components/form/EnzymeMultiSelect.jsx +34 -0
- package/src/components/form/GetRequestMultiSelect.jsx +107 -0
- package/src/components/form/LabelWithTooltip.jsx +16 -0
- package/src/components/form/PostRequestSelect.cy.jsx +70 -0
- package/src/components/form/PostRequestSelect.jsx +86 -0
- package/src/components/form/RequestStatusWrapper.jsx +17 -0
- package/src/components/form/RetryAlert.jsx +20 -0
- package/src/components/form/ServerErrorMessage.jsx +10 -0
- package/src/components/form/SubmitButtonBackendAPI.jsx +15 -0
- package/src/components/form/SubmitToDatabaseDialog.jsx +133 -0
- package/src/components/form/TextFieldValidate.jsx +67 -0
- package/src/components/form/ValidatedTextField.jsx +33 -0
- package/src/components/form/intermediates_disclaimer.svg +181 -0
- package/src/components/navigation/ButtonWithMenu.jsx +43 -0
- package/src/components/navigation/CustomTab.jsx +14 -0
- package/src/components/navigation/FeedbackDialog.jsx +34 -0
- package/src/components/navigation/GithubCornerRight.jsx +29 -0
- package/src/components/navigation/MainAppBar.css +26 -0
- package/src/components/navigation/MainAppBar.jsx +205 -0
- package/src/components/navigation/SelectExampleDialog.jsx +69 -0
- package/src/components/navigation/SelectTemplateDialog.jsx +107 -0
- package/src/components/navigation/TabPanel.jsx +28 -0
- package/src/components/navigation/VersionDialog.jsx +33 -0
- package/src/components/primers/CreatePrimerFromSequenceForm.jsx +42 -0
- package/src/components/primers/DownloadPrimersButton.jsx +104 -0
- package/src/components/primers/PrimerForm.css +14 -0
- package/src/components/primers/PrimerForm.jsx +107 -0
- package/src/components/primers/PrimerList.css +46 -0
- package/src/components/primers/PrimerList.cy.jsx +95 -0
- package/src/components/primers/PrimerList.jsx +126 -0
- package/src/components/primers/PrimerTableRow.cy.jsx +57 -0
- package/src/components/primers/PrimerTableRow.jsx +84 -0
- package/src/components/primers/SelectPrimerForm.jsx +66 -0
- package/src/components/primers/import_primers/ImportPrimersButton.jsx +101 -0
- package/src/components/primers/import_primers/ImportPrimersTable.jsx +51 -0
- package/src/components/primers/import_primers/PrimerDatabaseImportForm.jsx +107 -0
- package/src/components/primers/import_primers/styles.css +60 -0
- package/src/components/primers/primer_design/SequenceTabComponents/CollapsableLabel.jsx +31 -0
- package/src/components/primers/primer_design/SequenceTabComponents/GatewayRoiSelect.jsx +164 -0
- package/src/components/primers/primer_design/SequenceTabComponents/OrientationPicker.jsx +37 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignContext.jsx +369 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignEBIC.jsx +24 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignForm.jsx +29 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGatewayBP.jsx +36 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGibsonAssembly.jsx +26 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignHomologousRecombination.jsx +32 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignRestriction.jsx +25 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignSimplePair.jsx +25 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignStepper.jsx +53 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesigner.jsx +80 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerResultForm.jsx +49 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSettingsForm.jsx +68 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSpacerForm.jsx +84 -0
- package/src/components/primers/primer_design/SequenceTabComponents/RestrictionSpacerForm.jsx +85 -0
- package/src/components/primers/primer_design/SequenceTabComponents/StepNavigation.jsx +48 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelEBICSettings.jsx +216 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelResults.jsx +42 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelSelectRoi.jsx +61 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPannelSettings.jsx +59 -0
- package/src/components/primers/primer_design/SequenceTabComponents/primerDesignMinimalValues.json +5 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useEBICPrimerDesignSettings.js +31 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useEnzymePrimerDesignSettings.js +57 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useGatewayPrimerDesignSettings.js +18 -0
- package/src/components/primers/primer_design/SequenceTabComponents/usePrimerDesignSettings.js +31 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGatewayBP.jsx +88 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGibsonAssembly.jsx +65 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignHomologousRecombination.jsx +84 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignSourceForm.jsx +74 -0
- package/src/components/primers/primer_design/common/NoAttPSitesError.jsx +31 -0
- package/src/components/primers/primer_details/PCRTable.cy.jsx +51 -0
- package/src/components/primers/primer_details/PCRTable.jsx +35 -0
- package/src/components/primers/primer_details/Primer3Figure.jsx +25 -0
- package/src/components/primers/primer_details/PrimerDetailsTds.jsx +39 -0
- package/src/components/primers/primer_details/PrimerInfoIcon.cy.jsx +137 -0
- package/src/components/primers/primer_details/PrimerInfoIcon.jsx +132 -0
- package/src/components/primers/primer_details/TableSection.jsx +17 -0
- package/src/components/primers/primer_details/primerDetailsFormatting.js +3 -0
- package/src/components/primers/primer_details/useMultiplePrimerDetails.js +29 -0
- package/src/components/primers/primer_details/usePCRDetails.js +47 -0
- package/src/components/primers/primer_details/usePrimerDetailsEndpoints.js +49 -0
- package/src/components/primers/primer_details/useSinglePrimerSequenceDetails.js +25 -0
- package/src/components/primers/primersToTabularFile.js +49 -0
- package/src/components/primers/primersToTabularFile.test.js +108 -0
- package/src/components/settings/SettingsTab.cy.jsx +267 -0
- package/src/components/settings/SettingsTab.jsx +170 -0
- package/src/components/sources/AssemblyPlanDisplayer.cy.jsx +22 -0
- package/src/components/sources/AssemblyPlanDisplayer.jsx +27 -0
- package/src/components/sources/CollectionSource.jsx +97 -0
- package/src/components/sources/FinishedSource.jsx +397 -0
- package/src/components/sources/KnownSourceErrors.jsx +50 -0
- package/src/components/sources/MultipleInputsSelector.jsx +63 -0
- package/src/components/sources/MultipleOutputsSelector.jsx +63 -0
- package/src/components/sources/NewSourceBox.jsx +37 -0
- package/src/components/sources/PCRUnitForm.jsx +102 -0
- package/src/components/sources/SingleInputSelector.jsx +36 -0
- package/src/components/sources/Source.jsx +125 -0
- package/src/components/sources/SourceAnnotation.jsx +44 -0
- package/src/components/sources/SourceAssembly.jsx +201 -0
- package/src/components/sources/SourceBox.css +18 -0
- package/src/components/sources/SourceBox.jsx +60 -0
- package/src/components/sources/SourceCopySequence.jsx +38 -0
- package/src/components/sources/SourceDatabase.jsx +28 -0
- package/src/components/sources/SourceFile.jsx +188 -0
- package/src/components/sources/SourceGenomeRegion.cy.jsx +131 -0
- package/src/components/sources/SourceGenomeRegion.jsx +486 -0
- package/src/components/sources/SourceHomologousRecombination.jsx +125 -0
- package/src/components/sources/SourceKnownGenomeRegion.jsx +60 -0
- package/src/components/sources/SourceManuallyTyped.jsx +116 -0
- package/src/components/sources/SourcePCRorHybridization.jsx +165 -0
- package/src/components/sources/SourcePolymeraseExtension.jsx +44 -0
- package/src/components/sources/SourceRepositoryId.jsx +409 -0
- package/src/components/sources/SourceRestriction.jsx +41 -0
- package/src/components/sources/SourceReverseComplement.jsx +33 -0
- package/src/components/sources/SourceTypeSelector.jsx +94 -0
- package/src/components/sources/SubSequenceDisplayer.jsx +70 -0
- package/src/components/sources/VerifyDeleteDialog.jsx +23 -0
- package/src/components/sources/repositoryMetadata.js +14 -0
- package/src/components/verification/LoadFromDatabaseButton.jsx +90 -0
- package/src/components/verification/SequencingFileRow.jsx +34 -0
- package/src/components/verification/VerificationFileDialog.cy.jsx +176 -0
- package/src/components/verification/VerificationFileDialog.jsx +248 -0
- package/src/config/defaultMainEditorProps.js +44 -0
- package/src/hooks/useAlerts.js +16 -0
- package/src/hooks/useBackendAPI.js +51 -0
- package/src/hooks/useBackendRoute.js +22 -0
- package/src/hooks/useDatabase.js +18 -0
- package/src/hooks/useDragAndDropFile.js +31 -0
- package/src/hooks/useGatewaySites.js +40 -0
- package/src/hooks/useHttpClient.js +12 -0
- package/src/hooks/useLoadDatabaseFile.js +108 -0
- package/src/hooks/useStoreEditor.js +101 -0
- package/src/hooks/useValidateState.js +43 -0
- package/vitest.config.js +18 -0
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
import { it, vi } from 'vitest';
|
|
2
|
+
import { isEqual, cloneDeep } from 'lodash-es';
|
|
3
|
+
import eLabFTWInterface from './eLabFTWInterface';
|
|
4
|
+
import { makeSequenceMetadata } from './common';
|
|
5
|
+
import { error2String } from './utils';
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
name,
|
|
9
|
+
getSequenceLink,
|
|
10
|
+
getPrimerLink,
|
|
11
|
+
submitPrimerToDatabase,
|
|
12
|
+
getPrimer,
|
|
13
|
+
getSequenceName,
|
|
14
|
+
getSequencingFiles,
|
|
15
|
+
loadSequenceFromUrlParams,
|
|
16
|
+
isSubmissionDataValid,
|
|
17
|
+
submitSequenceToDatabase,
|
|
18
|
+
} = eLabFTWInterface;
|
|
19
|
+
|
|
20
|
+
let uniqueId = 2;
|
|
21
|
+
const getUniqueId = () => uniqueId++;
|
|
22
|
+
|
|
23
|
+
// bunch of constants for testing
|
|
24
|
+
const MAIN_RESOURCE_DATABASE_ID = getUniqueId();
|
|
25
|
+
const MAIN_RESOURCE_CATEGORY_ID = getUniqueId();
|
|
26
|
+
const MAIN_RESOURCE_TITLE = 'main-resource-title';
|
|
27
|
+
|
|
28
|
+
const MAIN_RESOURCE_SEQUENCE_FILE_DATABASE_ID = getUniqueId();
|
|
29
|
+
const MAIN_RESOURCE_SEQUENCE_FILE_NAME = 'sequence-file-1';
|
|
30
|
+
const MAIN_RESOURCE_SEQUENCE_FILE_CONTENT = 'sequence-file-content-1';
|
|
31
|
+
|
|
32
|
+
const MAIN_RESOURCE_HISTORY_FILE_DATABASE_ID = getUniqueId();
|
|
33
|
+
const HISTORY_FILE_INTERNAL_SEQUENCE_ID = getUniqueId();
|
|
34
|
+
const HISTORY_FILE_INTERNAL_PRIMER_ID1 = getUniqueId();
|
|
35
|
+
const HISTORY_FILE_INTERNAL_PRIMER_ID2 = getUniqueId();
|
|
36
|
+
const HISTORY_FILE_ANCESTOR_INTERNAL_SEQUENCE_ID = getUniqueId();
|
|
37
|
+
const HISTORY_FILE_ANCESTOR_DATABASE_ID = getUniqueId();
|
|
38
|
+
const HISTORY_FILE_INTERNAL_SOURCE_ID = HISTORY_FILE_INTERNAL_SEQUENCE_ID;
|
|
39
|
+
const HISTORY_FILE_ANCESTOR_INTERNAL_SOURCE_ID = getUniqueId();
|
|
40
|
+
// const
|
|
41
|
+
|
|
42
|
+
const SECONDARY_RESOURCE_DATABASE_ID1 = getUniqueId();
|
|
43
|
+
const SECONDARY_RESOURCE_DATABASE_ID2 = getUniqueId();
|
|
44
|
+
const SECONDARY_RESOURCE_CATEGORY_ID = getUniqueId();
|
|
45
|
+
const SEQUENCING_FILE_DATABASE_ID1 = getUniqueId();
|
|
46
|
+
const SEQUENCING_FILE_DATABASE_ID2 = getUniqueId();
|
|
47
|
+
const SEQUENCING_FILE_NAME1 = 'sequencing-file-1';
|
|
48
|
+
const SEQUENCING_FILE_NAME2 = 'sequencing-file-2';
|
|
49
|
+
const SEQUENCING_FILE_CONTENT1 = 'sequencing-file-content-1';
|
|
50
|
+
const SEQUENCING_FILE_CONTENT2 = 'sequencing-file-content-2';
|
|
51
|
+
|
|
52
|
+
const PRIMER1_NAME = 'primer1';
|
|
53
|
+
const PRIMER1_SEQUENCE = 'ACGT';
|
|
54
|
+
const PRIMER2_NAME = 'primer2';
|
|
55
|
+
const PRIMER2_SEQUENCE = 'AAAA';
|
|
56
|
+
|
|
57
|
+
// For testing purposes, we need an empty metadata object
|
|
58
|
+
export const emptyMetadata = JSON.stringify({ extra_fields: {} });
|
|
59
|
+
|
|
60
|
+
// Get the text content of a Blob
|
|
61
|
+
const getFileText = async (file) => {
|
|
62
|
+
const reader = new FileReader();
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
reader.onload = () => {
|
|
65
|
+
resolve(reader.result);
|
|
66
|
+
};
|
|
67
|
+
reader.readAsText(file);
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Take a scenario and return a new scenario with a network error
|
|
72
|
+
const mockNetworkError = (scenario) => ({
|
|
73
|
+
...scenario,
|
|
74
|
+
code: 'ERR_NETWORK',
|
|
75
|
+
response: undefined,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Take a scenario and return a new scenario with a not found error
|
|
79
|
+
const mockNotFound = (scenario) => ({
|
|
80
|
+
...scenario,
|
|
81
|
+
response: { status: 403, data: { description: 'Not found' } },
|
|
82
|
+
code: 'hello',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Mock the envValues module
|
|
86
|
+
vi.mock('./envValues', () => ({
|
|
87
|
+
readApiKey: '',
|
|
88
|
+
writeApiKey: '',
|
|
89
|
+
baseUrl: '',
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
// Mock the component imports
|
|
93
|
+
vi.mock('@mui/icons-material/Save', () => ({ default: 'SaveIcon' }));
|
|
94
|
+
vi.mock('@mui/icons-material/Link', () => ({ default: 'LinkIcon' }));
|
|
95
|
+
vi.mock('./GetSequenceFileAndDatabaseIdComponent', () => ({ default: 'GetSequenceFileAndDatabaseIdComponent' }));
|
|
96
|
+
vi.mock('./SubmitToDatabaseComponent', () => ({ default: 'SubmitToDatabaseComponent' }));
|
|
97
|
+
vi.mock('./PrimersNotInDatabaseComponent', () => ({ default: 'PrimersNotInDatabaseComponent' }));
|
|
98
|
+
vi.mock('./GetPrimerComponent', () => ({ default: 'GetPrimerComponent' }));
|
|
99
|
+
vi.mock('./LoadHistoryComponent', () => ({ default: 'LoadHistoryComponent' }));
|
|
100
|
+
|
|
101
|
+
// mockedScenarios is an array that contains mock API requests combined
|
|
102
|
+
// with their responses, for instance:
|
|
103
|
+
// [
|
|
104
|
+
// {
|
|
105
|
+
// method: 'get',
|
|
106
|
+
// url: '/api/v2/items/1',
|
|
107
|
+
// response: { data: { id: 1, title: 'test' } },
|
|
108
|
+
// },
|
|
109
|
+
// ]
|
|
110
|
+
// If a request is received by the mock client where the method, url and data
|
|
111
|
+
// match one of the scenarios, the response from the scenario is returned. Otherwise,
|
|
112
|
+
// the request will fail with an error. At the end of each test, we check that all
|
|
113
|
+
// scenarios were used and fail it otherwise. Scenarios are removed from the array
|
|
114
|
+
// when they are used, so they are not used twice.
|
|
115
|
+
|
|
116
|
+
let mockedScenarios;
|
|
117
|
+
|
|
118
|
+
beforeEach(() => {
|
|
119
|
+
mockedScenarios = [];
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
afterEach(() => {
|
|
123
|
+
// Check that all scenarios were used
|
|
124
|
+
|
|
125
|
+
if (mockedScenarios.length > 0) {
|
|
126
|
+
console.log('> mockedScenarios not used');
|
|
127
|
+
console.log(mockedScenarios);
|
|
128
|
+
}
|
|
129
|
+
expect(mockedScenarios).toHaveLength(0);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Get a scenario from the mockedScenarios array
|
|
133
|
+
async function getScenario(method, url, data, config) {
|
|
134
|
+
const nextScenario = mockedScenarios.shift();
|
|
135
|
+
expect(nextScenario).toBeDefined();
|
|
136
|
+
expect({ method: nextScenario.method, url: nextScenario.url, config: nextScenario.config }).toEqual({ method, url, config });
|
|
137
|
+
|
|
138
|
+
if (nextScenario.data instanceof FormData && data instanceof FormData) {
|
|
139
|
+
const scenarioComment = nextScenario.data.get('comment');
|
|
140
|
+
const requestComment = data.get('comment');
|
|
141
|
+
expect(scenarioComment).toBe(requestComment);
|
|
142
|
+
|
|
143
|
+
const scenarioFile = nextScenario.data.get('file');
|
|
144
|
+
const requestFile = data.get('file');
|
|
145
|
+
const scenarioFileText = await getFileText(scenarioFile);
|
|
146
|
+
const requestFileText = await getFileText(requestFile);
|
|
147
|
+
const filesAreJson = scenarioFileText.startsWith('{') && requestFileText.startsWith('{');
|
|
148
|
+
if (filesAreJson) {
|
|
149
|
+
const scenarioHistory = JSON.parse(scenarioFileText);
|
|
150
|
+
const requestHistory = JSON.parse(requestFileText);
|
|
151
|
+
expect(isEqual(scenarioHistory, requestHistory)).toBe(true);
|
|
152
|
+
} else if (scenarioFileText !== requestFileText) {
|
|
153
|
+
expect(scenarioFileText).toBe(requestFileText);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return nextScenario;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Common function to mock requests of a given method
|
|
160
|
+
function requestMocker(method) {
|
|
161
|
+
return async (...args) => {
|
|
162
|
+
let url;
|
|
163
|
+
let data;
|
|
164
|
+
let config;
|
|
165
|
+
if (method === 'get') {
|
|
166
|
+
[url, config] = args;
|
|
167
|
+
data = undefined;
|
|
168
|
+
} else if (method === 'delete') {
|
|
169
|
+
[url, config] = args;
|
|
170
|
+
data = config.data;
|
|
171
|
+
config = { headers: config.headers };
|
|
172
|
+
} else {
|
|
173
|
+
[url, data, config] = args;
|
|
174
|
+
}
|
|
175
|
+
const scenario = await getScenario(method, url, data, config);
|
|
176
|
+
expect(scenario).toBeDefined();
|
|
177
|
+
if (scenario.code === undefined && (scenario.response.status === undefined || scenario.response.status === 200)) {
|
|
178
|
+
return Promise.resolve(scenario.response);
|
|
179
|
+
}
|
|
180
|
+
return Promise.reject(scenario);
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Mock the eLabFTWHttpClient module
|
|
185
|
+
vi.mock('./common', async (importOriginal) => {
|
|
186
|
+
const actual = await importOriginal();
|
|
187
|
+
return {
|
|
188
|
+
...actual,
|
|
189
|
+
eLabFTWHttpClient: {
|
|
190
|
+
post: requestMocker('post'),
|
|
191
|
+
patch: requestMocker('patch'),
|
|
192
|
+
get: requestMocker('get'),
|
|
193
|
+
delete: requestMocker('delete'),
|
|
194
|
+
},
|
|
195
|
+
getELabFTWVersion: () => Promise.resolve(50200),
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('test name and links', () => {
|
|
200
|
+
it('test name', () => {
|
|
201
|
+
expect(name).toBe('eLabFTW');
|
|
202
|
+
});
|
|
203
|
+
it('test getSequenceLink', () => {
|
|
204
|
+
expect(getSequenceLink(1)).toBe('/database.php?mode=view&id=1');
|
|
205
|
+
});
|
|
206
|
+
it('test getPrimerLink', () => {
|
|
207
|
+
expect(getPrimerLink(1)).toBe('/database.php?mode=view&id=1');
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const mockCreateResource = {
|
|
212
|
+
method: 'post',
|
|
213
|
+
url: '/api/v2/items',
|
|
214
|
+
data: {
|
|
215
|
+
category_id: MAIN_RESOURCE_CATEGORY_ID,
|
|
216
|
+
tags: [],
|
|
217
|
+
},
|
|
218
|
+
config: { headers: {} },
|
|
219
|
+
response: { headers: { location: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}` } },
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const mockUpdateResource = {
|
|
223
|
+
method: 'patch',
|
|
224
|
+
url: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}`,
|
|
225
|
+
data: { title: MAIN_RESOURCE_TITLE },
|
|
226
|
+
config: { headers: {} },
|
|
227
|
+
response: {},
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const mockPrimerUpdate = {
|
|
231
|
+
...mockUpdateResource,
|
|
232
|
+
data: {
|
|
233
|
+
title: PRIMER1_NAME,
|
|
234
|
+
metadata: JSON.stringify({
|
|
235
|
+
extra_fields: {
|
|
236
|
+
sequence: {
|
|
237
|
+
type: 'text',
|
|
238
|
+
value: PRIMER1_SEQUENCE,
|
|
239
|
+
group_id: null,
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
}),
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const mockPrimerLink = {
|
|
248
|
+
method: 'post',
|
|
249
|
+
url: `/api/v2/items/${SECONDARY_RESOURCE_DATABASE_ID1}/items_links/${MAIN_RESOURCE_DATABASE_ID}`,
|
|
250
|
+
data: {},
|
|
251
|
+
config: { headers: {} },
|
|
252
|
+
response: {},
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const mockDeleteResource = {
|
|
256
|
+
method: 'delete',
|
|
257
|
+
url: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}`,
|
|
258
|
+
data: {},
|
|
259
|
+
config: { headers: {
|
|
260
|
+
'Content-Type': 'application/json',
|
|
261
|
+
} },
|
|
262
|
+
response: {},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const primerSubmissionPayload = {
|
|
266
|
+
submissionData: {
|
|
267
|
+
title: PRIMER1_NAME,
|
|
268
|
+
categoryId: MAIN_RESOURCE_CATEGORY_ID,
|
|
269
|
+
},
|
|
270
|
+
primer: { sequence: PRIMER1_SEQUENCE },
|
|
271
|
+
};
|
|
272
|
+
const primerSubmissionPayloadWithLinkedSequence = {
|
|
273
|
+
...primerSubmissionPayload,
|
|
274
|
+
linkedSequenceId: SECONDARY_RESOURCE_DATABASE_ID1,
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
describe('test submitPrimerToDatabase', () => {
|
|
278
|
+
it('without linked sequence', async () => {
|
|
279
|
+
mockedScenarios.push(mockCreateResource);
|
|
280
|
+
mockedScenarios.push(mockPrimerUpdate);
|
|
281
|
+
|
|
282
|
+
const result = await submitPrimerToDatabase(primerSubmissionPayload);
|
|
283
|
+
expect(result).toBe(MAIN_RESOURCE_DATABASE_ID);
|
|
284
|
+
});
|
|
285
|
+
it('with linked sequence', async () => {
|
|
286
|
+
mockedScenarios.push(mockCreateResource);
|
|
287
|
+
mockedScenarios.push(mockPrimerUpdate);
|
|
288
|
+
mockedScenarios.push(mockPrimerLink);
|
|
289
|
+
const result = await submitPrimerToDatabase(primerSubmissionPayloadWithLinkedSequence);
|
|
290
|
+
expect(result).toBe(MAIN_RESOURCE_DATABASE_ID);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('linking to missing sequence', async () => {
|
|
294
|
+
mockedScenarios.push(mockCreateResource);
|
|
295
|
+
mockedScenarios.push(mockPrimerUpdate);
|
|
296
|
+
mockedScenarios.push(mockNotFound(mockPrimerLink));
|
|
297
|
+
mockedScenarios.push(mockDeleteResource);
|
|
298
|
+
|
|
299
|
+
const result = submitPrimerToDatabase(primerSubmissionPayloadWithLinkedSequence);
|
|
300
|
+
expect(result).rejects.toThrow('Error linking to sequence: Not found');
|
|
301
|
+
});
|
|
302
|
+
it('fail linking network error', async () => {
|
|
303
|
+
mockedScenarios.push(mockCreateResource);
|
|
304
|
+
mockedScenarios.push(mockPrimerUpdate);
|
|
305
|
+
mockedScenarios.push(mockNetworkError(mockPrimerLink));
|
|
306
|
+
mockedScenarios.push(mockDeleteResource);
|
|
307
|
+
const result = submitPrimerToDatabase(primerSubmissionPayloadWithLinkedSequence);
|
|
308
|
+
expect(result).rejects.toThrow('Error linking to sequence: Network error: Cannot connect to eLabFTW');
|
|
309
|
+
});
|
|
310
|
+
it('fail naming network error', async () => {
|
|
311
|
+
mockedScenarios.push(mockCreateResource);
|
|
312
|
+
mockedScenarios.push(mockNetworkError(mockPrimerUpdate));
|
|
313
|
+
mockedScenarios.push(mockDeleteResource);
|
|
314
|
+
const result = submitPrimerToDatabase(primerSubmissionPayload);
|
|
315
|
+
expect(result).rejects.toThrow('Error naming primer: Network error: Cannot connect to eLabFTW');
|
|
316
|
+
});
|
|
317
|
+
it('fail creating primer', async () => {
|
|
318
|
+
mockedScenarios.push(mockNetworkError(mockCreateResource));
|
|
319
|
+
const result = submitPrimerToDatabase(primerSubmissionPayload);
|
|
320
|
+
expect(result).rejects.toThrow('Error creating primer: Network error: Cannot connect to eLabFTW');
|
|
321
|
+
});
|
|
322
|
+
it('fail removing primer if error occurs', async () => {
|
|
323
|
+
mockedScenarios.push(mockCreateResource);
|
|
324
|
+
mockedScenarios.push(mockNetworkError(mockPrimerUpdate));
|
|
325
|
+
mockedScenarios.push(mockNetworkError(mockDeleteResource));
|
|
326
|
+
const result = submitPrimerToDatabase(primerSubmissionPayload);
|
|
327
|
+
expect(result).rejects.toThrow(`There was an error (Network error: Cannot connect to eLabFTW) while trying to delete primer with id ${MAIN_RESOURCE_DATABASE_ID} after an error naming primer.`);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const mockPrimerGet = {
|
|
332
|
+
method: 'get',
|
|
333
|
+
url: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}`,
|
|
334
|
+
config: { headers: {} },
|
|
335
|
+
response: { data: {
|
|
336
|
+
id: MAIN_RESOURCE_DATABASE_ID,
|
|
337
|
+
title: PRIMER1_NAME,
|
|
338
|
+
metadata: makeSequenceMetadata(PRIMER1_SEQUENCE),
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const mockPrimerGetNoSequence = {
|
|
344
|
+
...mockPrimerGet,
|
|
345
|
+
response: { data: {
|
|
346
|
+
id: MAIN_RESOURCE_DATABASE_ID,
|
|
347
|
+
title: PRIMER1_NAME,
|
|
348
|
+
metadata: emptyMetadata,
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
describe('test getPrimer', () => {
|
|
354
|
+
it('get primer', async () => {
|
|
355
|
+
mockedScenarios.push(mockPrimerGet);
|
|
356
|
+
const result = await getPrimer(MAIN_RESOURCE_DATABASE_ID);
|
|
357
|
+
expect(result).toEqual({
|
|
358
|
+
name: PRIMER1_NAME,
|
|
359
|
+
database_id: MAIN_RESOURCE_DATABASE_ID,
|
|
360
|
+
sequence: PRIMER1_SEQUENCE,
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
it('primer with no sequence does not raise an error when loading', async () => {
|
|
364
|
+
mockedScenarios.push(mockPrimerGetNoSequence);
|
|
365
|
+
const result = await getPrimer(MAIN_RESOURCE_DATABASE_ID);
|
|
366
|
+
expect(result).toEqual({
|
|
367
|
+
name: PRIMER1_NAME,
|
|
368
|
+
database_id: MAIN_RESOURCE_DATABASE_ID,
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
it('fail getting primer - network error', async () => {
|
|
372
|
+
mockedScenarios.push(mockNetworkError(mockPrimerGet));
|
|
373
|
+
const result = getPrimer(MAIN_RESOURCE_DATABASE_ID);
|
|
374
|
+
expect(result).rejects.toThrow('Error getting primer: Network error: Cannot connect to eLabFTW');
|
|
375
|
+
});
|
|
376
|
+
it('fail getting primer - not found', async () => {
|
|
377
|
+
mockedScenarios.push(mockNotFound(mockPrimerGet));
|
|
378
|
+
const result = getPrimer(MAIN_RESOURCE_DATABASE_ID);
|
|
379
|
+
expect(result).rejects.toThrow(`Error getting primer with id ${MAIN_RESOURCE_DATABASE_ID}, it might have been deleted or you can no longer access it`);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const mockSequenceName = {
|
|
384
|
+
method: 'get',
|
|
385
|
+
url: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}`,
|
|
386
|
+
config: { headers: {} },
|
|
387
|
+
response: { data: { id: MAIN_RESOURCE_DATABASE_ID, title: MAIN_RESOURCE_TITLE } },
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
describe('test getSequenceName', () => {
|
|
391
|
+
it('get sequence name', async () => {
|
|
392
|
+
mockedScenarios.push(mockSequenceName);
|
|
393
|
+
const result = await getSequenceName(MAIN_RESOURCE_DATABASE_ID);
|
|
394
|
+
expect(result).toBe(MAIN_RESOURCE_TITLE);
|
|
395
|
+
});
|
|
396
|
+
it('fail getting sequence name - network error', async () => {
|
|
397
|
+
mockedScenarios.push(mockNetworkError(mockSequenceName));
|
|
398
|
+
const result = getSequenceName(MAIN_RESOURCE_DATABASE_ID);
|
|
399
|
+
expect(result).rejects.toThrow('Error getting sequence name: Network error: Cannot connect to eLabFTW');
|
|
400
|
+
});
|
|
401
|
+
it('fail getting sequence name - not found', async () => {
|
|
402
|
+
mockedScenarios.push(mockNotFound(mockSequenceName));
|
|
403
|
+
const result = getSequenceName(MAIN_RESOURCE_DATABASE_ID);
|
|
404
|
+
expect(result).rejects.toThrow(`Error getting name of sequence with id ${MAIN_RESOURCE_DATABASE_ID}, it might have been deleted or you can no longer access it`);
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const mockSequencingFilesFirstRequest = {
|
|
409
|
+
method: 'get',
|
|
410
|
+
url: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}/uploads`,
|
|
411
|
+
config: { headers: {} },
|
|
412
|
+
response: { data: [
|
|
413
|
+
{
|
|
414
|
+
id: SEQUENCING_FILE_DATABASE_ID1,
|
|
415
|
+
real_name: SEQUENCING_FILE_NAME1,
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
id: SEQUENCING_FILE_DATABASE_ID2,
|
|
419
|
+
real_name: SEQUENCING_FILE_NAME2,
|
|
420
|
+
},
|
|
421
|
+
] },
|
|
422
|
+
};
|
|
423
|
+
const mockSequencingFilesFirstFile = {
|
|
424
|
+
method: 'get',
|
|
425
|
+
url: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}/uploads/${SEQUENCING_FILE_DATABASE_ID1}?format=binary`,
|
|
426
|
+
config: { headers: {}, responseType: 'blob' },
|
|
427
|
+
response: { data: new Blob([SEQUENCING_FILE_CONTENT1], { type: 'application/octet-stream' }) },
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const mockSequencingFilesSecondFile = {
|
|
431
|
+
...mockSequencingFilesFirstFile,
|
|
432
|
+
url: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}/uploads/${SEQUENCING_FILE_DATABASE_ID2}?format=binary`,
|
|
433
|
+
response: { data: new Blob([SEQUENCING_FILE_CONTENT2], { type: 'application/octet-stream' }) },
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
describe('test getSequencingFiles', () => {
|
|
437
|
+
it('get sequencing files', async () => {
|
|
438
|
+
mockedScenarios.push(mockSequencingFilesFirstRequest);
|
|
439
|
+
mockedScenarios.push(mockSequencingFilesFirstFile);
|
|
440
|
+
mockedScenarios.push(mockSequencingFilesSecondFile);
|
|
441
|
+
const result = await getSequencingFiles(MAIN_RESOURCE_DATABASE_ID);
|
|
442
|
+
|
|
443
|
+
expect(result[0].name).toEqual(SEQUENCING_FILE_NAME1);
|
|
444
|
+
const reader = new FileReader();
|
|
445
|
+
reader.onload = () => {
|
|
446
|
+
expect(reader.result).toEqual(SEQUENCING_FILE_CONTENT1);
|
|
447
|
+
};
|
|
448
|
+
reader.readAsText(await result[0].getFile());
|
|
449
|
+
|
|
450
|
+
expect(result[1].name).toEqual(SEQUENCING_FILE_NAME2);
|
|
451
|
+
const reader2 = new FileReader();
|
|
452
|
+
reader2.onload = () => {
|
|
453
|
+
expect(reader2.result).toEqual(SEQUENCING_FILE_CONTENT2);
|
|
454
|
+
};
|
|
455
|
+
reader2.readAsText(await result[1].getFile());
|
|
456
|
+
});
|
|
457
|
+
it('get sequencing files - network error', async () => {
|
|
458
|
+
mockedScenarios.push(mockNetworkError(mockSequencingFilesFirstRequest));
|
|
459
|
+
const result = getSequencingFiles(MAIN_RESOURCE_DATABASE_ID);
|
|
460
|
+
expect(result).rejects.toThrow('Network error: Cannot connect to eLabFTW');
|
|
461
|
+
});
|
|
462
|
+
it('get sequencing files - not found', async () => {
|
|
463
|
+
mockedScenarios.push(mockNotFound(mockSequencingFilesFirstRequest));
|
|
464
|
+
const result = getSequencingFiles(MAIN_RESOURCE_DATABASE_ID);
|
|
465
|
+
expect(result).rejects.toThrow('Not found');
|
|
466
|
+
});
|
|
467
|
+
it('get sequencing files - getFile() - network error', async () => {
|
|
468
|
+
mockedScenarios.push(mockSequencingFilesFirstRequest);
|
|
469
|
+
mockedScenarios.push(mockNetworkError(mockSequencingFilesFirstFile));
|
|
470
|
+
const result = await getSequencingFiles(MAIN_RESOURCE_DATABASE_ID);
|
|
471
|
+
expect(result[0].getFile()).rejects.toThrow('Network error: Cannot connect to eLabFTW');
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
const mockFileInfo = {
|
|
476
|
+
method: 'get',
|
|
477
|
+
url: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}/uploads/${MAIN_RESOURCE_SEQUENCE_FILE_DATABASE_ID}`,
|
|
478
|
+
config: { headers: {} },
|
|
479
|
+
response: { data: { id: MAIN_RESOURCE_SEQUENCE_FILE_DATABASE_ID, real_name: MAIN_RESOURCE_SEQUENCE_FILE_NAME } },
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
const mockFileContent = {
|
|
483
|
+
method: 'get',
|
|
484
|
+
url: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}/uploads/${MAIN_RESOURCE_SEQUENCE_FILE_DATABASE_ID}?format=binary`,
|
|
485
|
+
config: { headers: {}, responseType: 'blob' },
|
|
486
|
+
response: { data: new Blob([MAIN_RESOURCE_SEQUENCE_FILE_CONTENT], { type: 'application/octet-stream' }) },
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
describe('test loadSequenceFromUrlParams', () => {
|
|
490
|
+
it('load sequence from url params', async () => {
|
|
491
|
+
mockedScenarios.push(mockFileInfo);
|
|
492
|
+
mockedScenarios.push(mockFileContent);
|
|
493
|
+
const result = await loadSequenceFromUrlParams({ item_id: MAIN_RESOURCE_DATABASE_ID, file_id: MAIN_RESOURCE_SEQUENCE_FILE_DATABASE_ID });
|
|
494
|
+
expect(result.file).toBeInstanceOf(File);
|
|
495
|
+
expect(result.file.name).toEqual(MAIN_RESOURCE_SEQUENCE_FILE_NAME);
|
|
496
|
+
expect(result.databaseId).toEqual(MAIN_RESOURCE_DATABASE_ID);
|
|
497
|
+
const reader = new FileReader();
|
|
498
|
+
reader.onload = () => {
|
|
499
|
+
expect(reader.result).toEqual(MAIN_RESOURCE_SEQUENCE_FILE_CONTENT);
|
|
500
|
+
};
|
|
501
|
+
reader.readAsText(result.file);
|
|
502
|
+
});
|
|
503
|
+
it('load sequence from url params - no item_id or file_id', async () => {
|
|
504
|
+
const result = await loadSequenceFromUrlParams({ file_id: MAIN_RESOURCE_SEQUENCE_FILE_DATABASE_ID });
|
|
505
|
+
expect(result).toBeNull();
|
|
506
|
+
const result2 = await loadSequenceFromUrlParams({ item_id: MAIN_RESOURCE_DATABASE_ID });
|
|
507
|
+
expect(result2).toBeNull();
|
|
508
|
+
const result3 = await loadSequenceFromUrlParams({});
|
|
509
|
+
expect(result3).toBeNull();
|
|
510
|
+
});
|
|
511
|
+
it('load sequence from url params - fileInfo network error', async () => {
|
|
512
|
+
mockedScenarios.push(mockNetworkError(mockFileInfo));
|
|
513
|
+
const result = loadSequenceFromUrlParams({ item_id: MAIN_RESOURCE_DATABASE_ID, file_id: MAIN_RESOURCE_SEQUENCE_FILE_DATABASE_ID });
|
|
514
|
+
expect(result).rejects.toThrow('Network error: Cannot connect to eLabFTW');
|
|
515
|
+
});
|
|
516
|
+
it('load sequence from url params - fileInfo not found', async () => {
|
|
517
|
+
mockedScenarios.push(mockNotFound(mockFileInfo));
|
|
518
|
+
const result = loadSequenceFromUrlParams({ item_id: MAIN_RESOURCE_DATABASE_ID, file_id: MAIN_RESOURCE_SEQUENCE_FILE_DATABASE_ID });
|
|
519
|
+
expect(result).rejects.toThrow('Not found');
|
|
520
|
+
});
|
|
521
|
+
it('load sequence from url params - fileContent network error', async () => {
|
|
522
|
+
mockedScenarios.push(mockFileInfo);
|
|
523
|
+
mockedScenarios.push(mockNetworkError(mockFileContent));
|
|
524
|
+
const result = loadSequenceFromUrlParams({ item_id: MAIN_RESOURCE_DATABASE_ID, file_id: MAIN_RESOURCE_SEQUENCE_FILE_DATABASE_ID });
|
|
525
|
+
expect(result).rejects.toThrow('Network error: Cannot connect to eLabFTW');
|
|
526
|
+
});
|
|
527
|
+
it('load sequence from url params - fileContent not found', async () => {
|
|
528
|
+
mockedScenarios.push(mockFileInfo);
|
|
529
|
+
mockedScenarios.push(mockNotFound(mockFileContent));
|
|
530
|
+
const result = loadSequenceFromUrlParams({ item_id: MAIN_RESOURCE_DATABASE_ID, file_id: MAIN_RESOURCE_SEQUENCE_FILE_DATABASE_ID });
|
|
531
|
+
expect(result).rejects.toThrow('Not found');
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
describe('test isSubmissionDataValid', () => {
|
|
536
|
+
it('isSubmissionDataValid', () => {
|
|
537
|
+
expect(isSubmissionDataValid({ title: MAIN_RESOURCE_TITLE, categoryId: MAIN_RESOURCE_CATEGORY_ID })).toBe(true);
|
|
538
|
+
expect(isSubmissionDataValid({ title: MAIN_RESOURCE_TITLE })).toBe(false);
|
|
539
|
+
expect(isSubmissionDataValid({ categoryId: MAIN_RESOURCE_CATEGORY_ID })).toBe(false);
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
describe('test error2String', () => {
|
|
544
|
+
it('error2String', () => {
|
|
545
|
+
expect(error2String({
|
|
546
|
+
code: 'ERR_NETWORK',
|
|
547
|
+
response: { status: 500, data: { description: 'Internal server error' } },
|
|
548
|
+
})).toBe('Network error: Cannot connect to eLabFTW');
|
|
549
|
+
expect(error2String({
|
|
550
|
+
code: 'OK',
|
|
551
|
+
response: { status: 500 },
|
|
552
|
+
})).toBe('Internal server error');
|
|
553
|
+
expect(error2String({
|
|
554
|
+
code: 'OK',
|
|
555
|
+
response: { status: 404, data: { description: 'error-message' } },
|
|
556
|
+
})).toBe('error-message');
|
|
557
|
+
expect(error2String({
|
|
558
|
+
})).toBe('Internal error, please contact the developers.');
|
|
559
|
+
expect(error2String({
|
|
560
|
+
code: 'OK',
|
|
561
|
+
response: { status: 404, data: { description: { dummy: 'dummy' } } },
|
|
562
|
+
})).toBe('Request error, please contact the developers.');
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const substate = {
|
|
567
|
+
primers: [],
|
|
568
|
+
sequences: [
|
|
569
|
+
{
|
|
570
|
+
id: HISTORY_FILE_INTERNAL_SEQUENCE_ID,
|
|
571
|
+
file_content: MAIN_RESOURCE_SEQUENCE_FILE_CONTENT,
|
|
572
|
+
},
|
|
573
|
+
],
|
|
574
|
+
sources: [
|
|
575
|
+
{
|
|
576
|
+
id: HISTORY_FILE_INTERNAL_SOURCE_ID
|
|
577
|
+
},
|
|
578
|
+
],
|
|
579
|
+
appInfo: {
|
|
580
|
+
backendVersion: '1.0.0',
|
|
581
|
+
schemaVersion: '1.0.0',
|
|
582
|
+
frontendVersion: '1.0.0',
|
|
583
|
+
},
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
// Helper function to match the format of cloning strategy
|
|
587
|
+
const expandAppInfo = (originalSubstate) => {
|
|
588
|
+
const newSubstate = cloneDeep(originalSubstate);
|
|
589
|
+
newSubstate.backend_version = originalSubstate.appInfo.backendVersion;
|
|
590
|
+
newSubstate.schema_version = originalSubstate.appInfo.schemaVersion;
|
|
591
|
+
newSubstate.frontend_version = originalSubstate.appInfo.frontendVersion;
|
|
592
|
+
delete newSubstate.appInfo;
|
|
593
|
+
return newSubstate;
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
const substateWithPrimers = {
|
|
597
|
+
...substate,
|
|
598
|
+
primers: [
|
|
599
|
+
{ id: HISTORY_FILE_INTERNAL_PRIMER_ID1, sequence: PRIMER1_SEQUENCE, name: PRIMER1_NAME },
|
|
600
|
+
{ id: HISTORY_FILE_INTERNAL_PRIMER_ID2, sequence: PRIMER2_SEQUENCE, name: PRIMER2_NAME, database_id: SECONDARY_RESOURCE_DATABASE_ID2 },
|
|
601
|
+
],
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
const substateWithAncestors = {
|
|
605
|
+
...substate,
|
|
606
|
+
sequences: [
|
|
607
|
+
...substate.sequences,
|
|
608
|
+
{
|
|
609
|
+
id: HISTORY_FILE_ANCESTOR_INTERNAL_SEQUENCE_ID,
|
|
610
|
+
},
|
|
611
|
+
],
|
|
612
|
+
sources: [
|
|
613
|
+
...substate.sources,
|
|
614
|
+
{
|
|
615
|
+
id: HISTORY_FILE_ANCESTOR_INTERNAL_SOURCE_ID,
|
|
616
|
+
database_id: HISTORY_FILE_ANCESTOR_DATABASE_ID,
|
|
617
|
+
},
|
|
618
|
+
],
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
const formData = new FormData();
|
|
622
|
+
formData.append('file', new Blob([MAIN_RESOURCE_SEQUENCE_FILE_CONTENT], { type: 'text/plain' }));
|
|
623
|
+
formData.append('comment', 'resource sequence - generated by OpenCloning');
|
|
624
|
+
|
|
625
|
+
const formData2 = new FormData();
|
|
626
|
+
formData2.append('file', new Blob([JSON.stringify(expandAppInfo(substate))], { type: 'text/plain' }));
|
|
627
|
+
formData2.append('comment', 'history file - generated by OpenCloning');
|
|
628
|
+
|
|
629
|
+
const formDataWithAncestors = new FormData();
|
|
630
|
+
formDataWithAncestors.append('file', new Blob([JSON.stringify(expandAppInfo(substateWithAncestors))], { type: 'text/plain' }));
|
|
631
|
+
formDataWithAncestors.append('comment', 'history file - generated by OpenCloning');
|
|
632
|
+
|
|
633
|
+
// When the new primer is created, the history is updated with the new primer database id,
|
|
634
|
+
// so the submitted file must contain the new primer database id
|
|
635
|
+
const substateWithPrimersAndNewPrimerDatabaseId = JSON.parse(JSON.stringify(expandAppInfo(substateWithPrimers)));
|
|
636
|
+
substateWithPrimersAndNewPrimerDatabaseId.primers[0].database_id = SECONDARY_RESOURCE_DATABASE_ID1;
|
|
637
|
+
const formDataWithPrimers = new FormData();
|
|
638
|
+
formDataWithPrimers.append('file', new Blob([JSON.stringify(substateWithPrimersAndNewPrimerDatabaseId)], { type: 'text/plain' }));
|
|
639
|
+
formDataWithPrimers.append('comment', 'history file - generated by OpenCloning');
|
|
640
|
+
|
|
641
|
+
const mockUploadSequenceFile = {
|
|
642
|
+
method: 'post',
|
|
643
|
+
url: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}/uploads`,
|
|
644
|
+
data: formData,
|
|
645
|
+
config: { headers: {} },
|
|
646
|
+
response: { headers: { location: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}/uploads/${MAIN_RESOURCE_SEQUENCE_FILE_DATABASE_ID}` } },
|
|
647
|
+
};
|
|
648
|
+
const mockUploadHistoryFile = {
|
|
649
|
+
method: 'post',
|
|
650
|
+
url: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}/uploads`,
|
|
651
|
+
data: formData2,
|
|
652
|
+
config: { headers: {} },
|
|
653
|
+
response: { headers: { location: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}/uploads/${MAIN_RESOURCE_HISTORY_FILE_DATABASE_ID}` } },
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
const mockUploadHistoryFileWithPrimers = {
|
|
657
|
+
...mockUploadHistoryFile,
|
|
658
|
+
data: formDataWithPrimers,
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
const mockUploadHistoryFileWithAncestors = {
|
|
662
|
+
...mockUploadHistoryFile,
|
|
663
|
+
data: formDataWithAncestors,
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
const submissionPayload = {
|
|
667
|
+
submissionData: { title: MAIN_RESOURCE_TITLE, categoryId: MAIN_RESOURCE_CATEGORY_ID },
|
|
668
|
+
substate,
|
|
669
|
+
id: HISTORY_FILE_INTERNAL_SEQUENCE_ID,
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
const submissionPayloadWithPrimers = {
|
|
673
|
+
...submissionPayload,
|
|
674
|
+
submissionData: {
|
|
675
|
+
...submissionPayload.submissionData,
|
|
676
|
+
primerCategoryId: SECONDARY_RESOURCE_CATEGORY_ID,
|
|
677
|
+
},
|
|
678
|
+
substate: substateWithPrimers,
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
const submissionPayloadWithAncestors = {
|
|
682
|
+
...submissionPayload,
|
|
683
|
+
substate: substateWithAncestors,
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
const mockCreateSecondaryResource = {
|
|
687
|
+
...mockCreateResource,
|
|
688
|
+
data: {
|
|
689
|
+
tags: [],
|
|
690
|
+
category_id: SECONDARY_RESOURCE_CATEGORY_ID,
|
|
691
|
+
},
|
|
692
|
+
response: { headers: { location: `/api/v2/items/${SECONDARY_RESOURCE_DATABASE_ID1}` } },
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
const mockLinkNewPrimer = {
|
|
696
|
+
...mockPrimerLink,
|
|
697
|
+
url: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}/items_links/${SECONDARY_RESOURCE_DATABASE_ID1}`,
|
|
698
|
+
};
|
|
699
|
+
const mockLinkExistingPrimer = {
|
|
700
|
+
...mockPrimerLink,
|
|
701
|
+
url: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}/items_links/${SECONDARY_RESOURCE_DATABASE_ID2}`,
|
|
702
|
+
};
|
|
703
|
+
const mockLinkToAncestor = {
|
|
704
|
+
...mockPrimerLink,
|
|
705
|
+
url: `/api/v2/items/${MAIN_RESOURCE_DATABASE_ID}/items_links/${HISTORY_FILE_ANCESTOR_DATABASE_ID}`,
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
const mockUpdateNewPrimer = {
|
|
709
|
+
...mockPrimerUpdate,
|
|
710
|
+
url: `/api/v2/items/${SECONDARY_RESOURCE_DATABASE_ID1}`,
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
const mockDeleteNewPrimer = {
|
|
714
|
+
...mockDeleteResource,
|
|
715
|
+
url: `/api/v2/items/${SECONDARY_RESOURCE_DATABASE_ID1}`,
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
describe('test submitSequenceToDatabase', () => {
|
|
719
|
+
it('submitSequenceToDatabase', async () => {
|
|
720
|
+
mockedScenarios.push(mockCreateResource);
|
|
721
|
+
mockedScenarios.push(mockUpdateResource);
|
|
722
|
+
mockedScenarios.push(mockUploadSequenceFile);
|
|
723
|
+
mockedScenarios.push(mockUploadHistoryFile);
|
|
724
|
+
const { primerMappings, databaseId } = await submitSequenceToDatabase(submissionPayload);
|
|
725
|
+
expect(primerMappings).toEqual([]);
|
|
726
|
+
expect(databaseId).toEqual(MAIN_RESOURCE_DATABASE_ID);
|
|
727
|
+
});
|
|
728
|
+
it('submitSequenceToDatabase - create resource network error', async () => {
|
|
729
|
+
mockedScenarios.push(mockNetworkError(mockCreateResource));
|
|
730
|
+
const result = submitSequenceToDatabase(submissionPayload);
|
|
731
|
+
expect(result).rejects.toThrow('Error creating resource: Network error: Cannot connect to eLabFTW');
|
|
732
|
+
});
|
|
733
|
+
it('submitSequenceToDatabase - rename resource network error', async () => {
|
|
734
|
+
mockedScenarios.push(mockCreateResource);
|
|
735
|
+
mockedScenarios.push(mockNetworkError(mockUpdateResource));
|
|
736
|
+
mockedScenarios.push(mockDeleteResource);
|
|
737
|
+
const result = submitSequenceToDatabase(submissionPayload);
|
|
738
|
+
expect(result).rejects.toThrow('Error setting resource title: Network error: Cannot connect to eLabFTW');
|
|
739
|
+
});
|
|
740
|
+
it('submitSequenceToDatabase - submit sequence file network error', async () => {
|
|
741
|
+
mockedScenarios.push(mockCreateResource);
|
|
742
|
+
mockedScenarios.push(mockUpdateResource);
|
|
743
|
+
mockedScenarios.push(mockNetworkError(mockUploadSequenceFile));
|
|
744
|
+
mockedScenarios.push(mockDeleteResource);
|
|
745
|
+
const result = submitSequenceToDatabase(submissionPayload);
|
|
746
|
+
expect(result).rejects.toThrow('Error uploading sequence file: Network error: Cannot connect to eLabFTW');
|
|
747
|
+
});
|
|
748
|
+
it('submitSequenceToDatabase - submit history file network error', async () => {
|
|
749
|
+
mockedScenarios.push(mockCreateResource);
|
|
750
|
+
mockedScenarios.push(mockUpdateResource);
|
|
751
|
+
mockedScenarios.push(mockUploadSequenceFile);
|
|
752
|
+
mockedScenarios.push(mockNetworkError(mockUploadHistoryFile));
|
|
753
|
+
mockedScenarios.push(mockDeleteResource);
|
|
754
|
+
const result = submitSequenceToDatabase(submissionPayload);
|
|
755
|
+
expect(result).rejects.toThrow('Error uploading history file: Network error: Cannot connect to eLabFTW');
|
|
756
|
+
});
|
|
757
|
+
it('submitSequenceToDatabase - submit sequence with primers', async () => {
|
|
758
|
+
mockedScenarios.push(mockCreateResource);
|
|
759
|
+
mockedScenarios.push(mockUpdateResource);
|
|
760
|
+
mockedScenarios.push(mockLinkExistingPrimer);
|
|
761
|
+
mockedScenarios.push(mockCreateSecondaryResource);
|
|
762
|
+
mockedScenarios.push(mockUpdateNewPrimer);
|
|
763
|
+
mockedScenarios.push(mockLinkNewPrimer);
|
|
764
|
+
mockedScenarios.push(mockUploadSequenceFile);
|
|
765
|
+
mockedScenarios.push(mockUploadHistoryFileWithPrimers);
|
|
766
|
+
const { primerMappings, databaseId } = await submitSequenceToDatabase(submissionPayloadWithPrimers);
|
|
767
|
+
expect(primerMappings).toEqual([
|
|
768
|
+
{
|
|
769
|
+
localId: HISTORY_FILE_INTERNAL_PRIMER_ID1,
|
|
770
|
+
databaseId: SECONDARY_RESOURCE_DATABASE_ID1,
|
|
771
|
+
},
|
|
772
|
+
]);
|
|
773
|
+
expect(databaseId).toEqual(MAIN_RESOURCE_DATABASE_ID);
|
|
774
|
+
});
|
|
775
|
+
it('submitSequenceToDatabase - submit sequence with primers network error when linking primer - deletes both resource and primers', async () => {
|
|
776
|
+
mockedScenarios.push(mockCreateResource);
|
|
777
|
+
mockedScenarios.push(mockUpdateResource);
|
|
778
|
+
mockedScenarios.push(mockLinkExistingPrimer);
|
|
779
|
+
mockedScenarios.push(mockCreateSecondaryResource);
|
|
780
|
+
mockedScenarios.push(mockUpdateNewPrimer);
|
|
781
|
+
mockedScenarios.push(mockNetworkError(mockLinkNewPrimer));
|
|
782
|
+
mockedScenarios.push(mockDeleteNewPrimer);
|
|
783
|
+
mockedScenarios.push(mockDeleteResource);
|
|
784
|
+
|
|
785
|
+
const result = submitSequenceToDatabase(submissionPayloadWithPrimers);
|
|
786
|
+
expect(result).rejects.toThrow('Error submitting new primers: Error linking to sequence: Network error: Cannot connect to eLabFTW');
|
|
787
|
+
});
|
|
788
|
+
it('submitSequenceToDatabase - submit sequence with primers network error after linking primer - deletes both resource and primers', async () => {
|
|
789
|
+
mockedScenarios.push(mockCreateResource);
|
|
790
|
+
mockedScenarios.push(mockUpdateResource);
|
|
791
|
+
mockedScenarios.push(mockLinkExistingPrimer);
|
|
792
|
+
mockedScenarios.push(mockCreateSecondaryResource);
|
|
793
|
+
mockedScenarios.push(mockUpdateNewPrimer);
|
|
794
|
+
mockedScenarios.push(mockLinkNewPrimer);
|
|
795
|
+
mockedScenarios.push(mockNetworkError(mockUploadSequenceFile));
|
|
796
|
+
mockedScenarios.push(mockDeleteNewPrimer);
|
|
797
|
+
mockedScenarios.push(mockDeleteResource);
|
|
798
|
+
|
|
799
|
+
const result = submitSequenceToDatabase(submissionPayloadWithPrimers);
|
|
800
|
+
expect(result).rejects.toThrow('Error uploading sequence file: Network error: Cannot connect to eLabFTW');
|
|
801
|
+
});
|
|
802
|
+
it('submitSequenceToDatabase - submit sequence with ancestors', async () => {
|
|
803
|
+
mockedScenarios.push(mockCreateResource);
|
|
804
|
+
mockedScenarios.push(mockUpdateResource);
|
|
805
|
+
mockedScenarios.push(mockLinkToAncestor);
|
|
806
|
+
mockedScenarios.push(mockUploadSequenceFile);
|
|
807
|
+
mockedScenarios.push(mockUploadHistoryFileWithAncestors);
|
|
808
|
+
const { primerMappings, databaseId } = await submitSequenceToDatabase(submissionPayloadWithAncestors);
|
|
809
|
+
expect(primerMappings).toEqual([]);
|
|
810
|
+
expect(databaseId).toEqual(MAIN_RESOURCE_DATABASE_ID);
|
|
811
|
+
});
|
|
812
|
+
it('submitSequenceToDatabase - error deleting resource after error', async () => {
|
|
813
|
+
mockedScenarios.push(mockCreateResource);
|
|
814
|
+
mockedScenarios.push(mockUpdateResource);
|
|
815
|
+
mockedScenarios.push(mockNetworkError(mockUploadSequenceFile));
|
|
816
|
+
mockedScenarios.push(mockNetworkError(mockDeleteResource));
|
|
817
|
+
const result = submitSequenceToDatabase(submissionPayload);
|
|
818
|
+
expect(result).rejects.toThrow(`There was an error (Network error: Cannot connect to eLabFTW) while trying to delete the sequence with id ${MAIN_RESOURCE_DATABASE_ID} after an error uploading sequence file.`);
|
|
819
|
+
});
|
|
820
|
+
it('submitSequenceToDatabase - error deleting primer after error', async () => {
|
|
821
|
+
mockedScenarios.push(mockCreateResource);
|
|
822
|
+
mockedScenarios.push(mockUpdateResource);
|
|
823
|
+
mockedScenarios.push(mockLinkExistingPrimer);
|
|
824
|
+
mockedScenarios.push(mockCreateSecondaryResource);
|
|
825
|
+
mockedScenarios.push(mockUpdateNewPrimer);
|
|
826
|
+
mockedScenarios.push(mockLinkNewPrimer);
|
|
827
|
+
mockedScenarios.push(mockNetworkError(mockUploadSequenceFile));
|
|
828
|
+
mockedScenarios.push(mockNetworkError(mockDeleteNewPrimer));
|
|
829
|
+
|
|
830
|
+
const result = submitSequenceToDatabase(submissionPayloadWithPrimers);
|
|
831
|
+
expect(result).rejects.toThrow(`There was an error (Network error: Cannot connect to eLabFTW) while trying to delete newly created primers linked to sequence with id ${MAIN_RESOURCE_DATABASE_ID} after an error uploading sequence file.`);
|
|
832
|
+
});
|
|
833
|
+
it('throws error if sequence already has a database_id', () => {
|
|
834
|
+
const submissionPayloadWithDatabaseId = JSON.parse(JSON.stringify(submissionPayload));
|
|
835
|
+
submissionPayloadWithDatabaseId.substate.sources[0].database_id = '123';
|
|
836
|
+
const result = submitSequenceToDatabase(submissionPayloadWithDatabaseId);
|
|
837
|
+
expect(result).rejects.toThrow('Sequence already has a database_id');
|
|
838
|
+
});
|
|
839
|
+
});
|