diff --git a/ICD_test_stages/file_browser.tests b/ICD_test_stages/file_browser.tests index be92334..d12258a 100644 --- a/ICD_test_stages/file_browser.tests +++ b/ICD_test_stages/file_browser.tests @@ -3,3 +3,4 @@ src/test/GET_FILELIST_ROOTPATH_CONCURRENT.test.ts src/test/FILEINFO_FITS_MULTIHDU.test.ts src/test/FILEINFO_EXCEPTIONS.test.ts src/test/OPEN_SWAPPED_IMAGES.test.ts +src/test/SAVE_IMAGE_OVERWRITE.test.ts diff --git a/ICD_test_stages/region_manipulation.tests b/ICD_test_stages/region_manipulation.tests index de9ae89..378521d 100644 --- a/ICD_test_stages/region_manipulation.tests +++ b/ICD_test_stages/region_manipulation.tests @@ -6,3 +6,4 @@ src/test/CASA_REGION_EXPORT.test.ts src/test/DS9_REGION_EXPORT.test.ts src/test/DS9_REGION_IMPORT_EXCEPTION.test.ts src/test/DS9_REGION_IMPORT_EXPORT.test.ts +src/test/EXPORT_REGION_OVERWRITE.test.ts diff --git a/docs/source/casa_region.rst b/docs/source/casa_region.rst index c9af5f4..7fe59f0 100644 --- a/docs/source/casa_region.rst +++ b/docs/source/casa_region.rst @@ -516,3 +516,88 @@ This test verifies that hand-created regions can be exported to CASA region form - IMPORT_REGION_ACK.success = True - Length of regions = 16 - Last region ID = 48 + +EXPORT_REGION_OVERWRITE +~~~~~~~~~~~~~~~~~~~~~~~ + +See the `source code `__. + +This test verifies that the backend refuses to export regions to a path where doing so would destroy +something which is already there. The check is made before the region file is written and does not +depend on the region format, so CRTF is used here to cover it. + +A directory can never be overwritten, so that case is sent with ``overwrite = true`` to show that +the refusal does not depend on it. An existing file is a case the frontend may retry after asking +the user, so it is sent with ``overwrite = false`` and the backend is expected to set +``overwrite_confirmation_required = true`` rather than simply failing. + +1. Frontend sends: **CLOSE_FILE** (``CloseFile``) and **OPEN_FILE** (``OpenFile``) + + .. code-block:: protobuf + + directory = "set_QA" + file = "M17_SWex.image" + hdu = "" + file_id = 0 + render_mode = RASTER + +2. Backend returns: **OPEN_FILE_ACK** (``OpenFileAck``) and **REGION_HISTOGRAM_DATA** + +:red-text:`Check 1:` the OPEN_FILE_ACK should satisfy: + + - OPEN_FILE_ACK.success = True + - OPEN_FILE_ACK.fileInfo.name = "M17_SWex.image" + +3. Frontend sends: **SET_REGION** (``SetRegion``) to create a region to export + + .. code-block:: protobuf + + file_id = 0 + region_id = -1 + region_type = RECTANGLE + control_points = [{x: 200.0, y: 400.0}, {x: 100.0, y: 100.0}] + rotation = 0.0 + +:red-text:`Check 2:` SET_REGION_ACK.success = True + +**Case 1: The export path is an existing directory** + +4. Frontend sends: **EXPORT_REGION** (``ExportRegion``) + + .. code-block:: protobuf + + directory = "set_QA" + file = "regionTest" + type = CRTF + coord_type = PIXEL + file_id = 0 + overwrite = true + +5. Backend returns: **EXPORT_REGION_ACK** (``ExportRegionAck``) + +:red-text:`Check 3:` the EXPORT_REGION_ACK should satisfy: + + - EXPORT_REGION_ACK.success = False + - EXPORT_REGION_ACK.message = "Export region failed: cannot overwrite existing directory." + - EXPORT_REGION_ACK.overwrite_confirmation_required = False + +**Case 2: The export path is an existing file** + +6. Frontend sends: **EXPORT_REGION** (``ExportRegion``) + + .. code-block:: protobuf + + directory = "set_QA/regionTest" + file = "M17_SWex_regionSet1_pix.crtf" + type = CRTF + coord_type = PIXEL + file_id = 0 + overwrite = false + +7. Backend returns: **EXPORT_REGION_ACK** (``ExportRegionAck``) + +:red-text:`Check 4:` the EXPORT_REGION_ACK should satisfy: + + - EXPORT_REGION_ACK.success = False + - EXPORT_REGION_ACK.message = "Export region failed: cannot overwrite existing file." + - EXPORT_REGION_ACK.overwrite_confirmation_required = True diff --git a/docs/source/save_image.rst b/docs/source/save_image.rst index 5625d1d..acb1864 100644 --- a/docs/source/save_image.rst +++ b/docs/source/save_image.rst @@ -493,3 +493,172 @@ This test verifies that appropriate error messages are returned when attempting :red-text:`Check 4:` the SAVE_FILE response should satisfy: - Error message should contain "The selected region is entirely outside the image." + +SAVE_IMAGE_OVERWRITE +~~~~~~~~~~~~~~~~~~~~ + +See the `source code `__. + +This test verifies that the backend refuses to save an image to a path where doing so would destroy +something which is already there, and that it asks the frontend for confirmation in the cases where +the user may legitimately want to overwrite. + +Seven requests are sent, one per refusal. The first three must be refused whatever ``overwrite`` +says, so they are sent with ``overwrite = true`` to show that the refusal does not depend on it. The +remaining four are the ones the frontend may retry after asking the user, so they are sent with +``overwrite = false`` and the backend is expected to set ``overwrite_confirmation_required = true`` +rather than simply failing. + +.. note:: + + Case 5 needs a symbolic link on disk, which the test creates before connecting and removes + afterwards. It is the only ICD test which needs to know the absolute path of the backend's top + level folder, because no CARTA message exposes it — ``FILE_LIST_RESPONSE.directory`` is relative. + Set ``path.imageRoot`` in ``src/test/config.json`` to the folder the backend was started with, + otherwise the test aborts before sending any request. + +1. Frontend sends: **CLOSE_FILE** (``CloseFile``) and **OPEN_FILE** (``OpenFile``) + + .. code-block:: protobuf + + directory = "set_QA" + file = "M17_SWex.fits" + hdu = "" + file_id = 0 + render_mode = RASTER + +2. Backend returns: **OPEN_FILE_ACK** (``OpenFileAck``) and **REGION_HISTOGRAM_DATA** + +:red-text:`Check 1:` the OPEN_FILE_ACK should satisfy: + + - OPEN_FILE_ACK.success = True + - OPEN_FILE_ACK.fileInfo.name = "M17_SWex.fits" + +**Case 1: No file name** + +3. Frontend sends: **SAVE_FILE** (``SaveFile``) + + .. code-block:: protobuf + + file_id = 0 + output_file_directory = "set_QA/tmp" + output_file_name = "" + output_file_type = CASA + overwrite = true + +:red-text:`Check 2:` the SAVE_FILE_ACK should satisfy: + + - SAVE_FILE_ACK.success = False + - SAVE_FILE_ACK.message = "Cannot save image with no filename." + - SAVE_FILE_ACK.overwrite_confirmation_required = False + +**Case 2: The output path is the image which is currently open** + +4. Frontend sends: **SAVE_FILE** (``SaveFile``) + + .. code-block:: protobuf + + file_id = 0 + output_file_directory = "set_QA" + output_file_name = "M17_SWex.fits" + output_file_type = FITS + overwrite = true + +:red-text:`Check 3:` the SAVE_FILE_ACK should satisfy: + + - SAVE_FILE_ACK.success = False + - SAVE_FILE_ACK.message = "Cannot overwrite the source image." + - SAVE_FILE_ACK.overwrite_confirmation_required = False + +**Case 3: The output path is a directory which is not an image** + +5. Frontend sends: **SAVE_FILE** (``SaveFile``) + + .. code-block:: protobuf + + file_id = 0 + output_file_directory = "set_QA" + output_file_name = "regionTest" + output_file_type = CASA + overwrite = true + +:red-text:`Check 4:` the SAVE_FILE_ACK should satisfy: + + - SAVE_FILE_ACK.success = False + - SAVE_FILE_ACK.message = "Cannot overwrite existing directory." + - SAVE_FILE_ACK.overwrite_confirmation_required = False + +**Case 4: The output path is a file which is not an image** + +6. Frontend sends: **SAVE_FILE** (``SaveFile``) + + .. code-block:: protobuf + + file_id = 0 + output_file_directory = "set_QA/regionTest" + output_file_name = "M17_SWex_regionSet1_pix.crtf" + output_file_type = CASA + overwrite = false + +:red-text:`Check 5:` the SAVE_FILE_ACK should satisfy: + + - SAVE_FILE_ACK.success = False + - SAVE_FILE_ACK.message = "Cannot overwrite existing file or symlink." + - SAVE_FILE_ACK.overwrite_confirmation_required = True + +**Case 5: The output path is a symbolic link** + +7. Frontend sends: **SAVE_FILE** (``SaveFile``) + + .. code-block:: protobuf + + file_id = 0 + output_file_directory = "set_QA/tmp" + output_file_name = "save_image_overwrite_symlink" + output_file_type = CASA + overwrite = false + +:red-text:`Check 6:` the SAVE_FILE_ACK should satisfy: + + - SAVE_FILE_ACK.success = False + - SAVE_FILE_ACK.message = "Cannot overwrite existing file or symlink." + - SAVE_FILE_ACK.overwrite_confirmation_required = True + + (A symbolic link is refused separately because writing to it would replace the link itself + rather than the file it points at.) + +**Case 6: The output path is an existing image file** + +8. Frontend sends: **SAVE_FILE** (``SaveFile``) + + .. code-block:: protobuf + + file_id = 0 + output_file_directory = "set_QA" + output_file_name = "M17_SWex.hdf5" + output_file_type = HDF5 + overwrite = false + +:red-text:`Check 7:` the SAVE_FILE_ACK should satisfy: + + - SAVE_FILE_ACK.success = False + - SAVE_FILE_ACK.message = "Cannot overwrite existing image." + - SAVE_FILE_ACK.overwrite_confirmation_required = True + +**Case 7: The output path is an existing image directory** + +9. Frontend sends: **SAVE_FILE** (``SaveFile``) + + .. code-block:: protobuf + + file_id = 0 + output_file_directory = "set_QA" + output_file_name = "M17_SWex.image" + output_file_type = CASA + overwrite = false + +:red-text:`Check 8:` the SAVE_FILE_ACK should satisfy: + + - SAVE_FILE_ACK.success = False + - SAVE_FILE_ACK.message = "Cannot overwrite existing image." + - SAVE_FILE_ACK.overwrite_confirmation_required = True diff --git a/protobuf b/protobuf index 9b181d2..41389cc 160000 --- a/protobuf +++ b/protobuf @@ -1 +1 @@ -Subproject commit 9b181d2811807bec2af667eb5330aa6567bbf428 +Subproject commit 41389cc9be88b97062f38d226f753cfc27e2ee3d diff --git a/src/test/EXPORT_REGION_OVERWRITE.test.ts b/src/test/EXPORT_REGION_OVERWRITE.test.ts new file mode 100644 index 0000000..23b5c76 --- /dev/null +++ b/src/test/EXPORT_REGION_OVERWRITE.test.ts @@ -0,0 +1,186 @@ +import { CARTA } from 'carta-protobuf'; +import { checkConnection, Stream } from './MyClient'; +import { MessageController } from './MessageController'; +import config from './config.json'; + +let testServerUrl: string = config.serverURL0; +let testSubdirectory: string = config.path.QA; +let regionSubdirectory: string = config.path.region; +let connectTimeout: number = config.timeout.connection; +let openFileTimeout: number = config.timeout.openFile; +let regionTimeout: number = config.timeout.region; +let exportTimeout: number = config.timeout.export; + +interface AssertItem { + fileOpen: CARTA.IOpenFile; + setRegion: CARTA.ISetRegion; + regionStyle: CARTA.IRegionStyle; + exportRegion: CARTA.IExportRegion[]; + exportRegionAck: CARTA.IExportRegionAck[]; +} + +let assertItem: AssertItem = { + fileOpen: { + directory: testSubdirectory, + file: 'M17_SWex.image', + hdu: '', + fileId: 0, + renderMode: CARTA.RenderMode.RASTER, + }, + setRegion: { + fileId: 0, + regionId: -1, + regionInfo: { + regionType: CARTA.RegionType.RECTANGLE, + rotation: 0, + controlPoints: [ + { x: 200.0, y: 400.0 }, + { x: 100.0, y: 100.0 }, + ], + }, + }, + regionStyle: { + name: '', + color: '#2EE6D6', + lineWidth: 2, + dashList: [], + }, + exportRegion: [ + { + // The export path resolves to an existing directory + directory: testSubdirectory, + file: 'regionTest', + type: CARTA.FileType.CRTF, + coordType: CARTA.CoordinateType.PIXEL, + fileId: 0, + overwrite: true, + }, + { + // The export path resolves to an existing file + directory: regionSubdirectory, + file: 'M17_SWex_regionSet1_pix.crtf', + type: CARTA.FileType.CRTF, + coordType: CARTA.CoordinateType.PIXEL, + fileId: 0, + overwrite: false, + }, + ], + exportRegionAck: [ + { + success: false, + message: 'Export region failed: cannot overwrite existing directory.', + overwriteConfirmationRequired: false, + }, + { + success: false, + message: 'Export region failed: cannot overwrite existing file.', + overwriteConfirmationRequired: true, + }, + ], +}; + +let basepath: string; +describe('EXPORT_REGION_OVERWRITE: Exporting regions to a path which cannot be overwritten', () => { + const msgController = MessageController.Instance; + + // The backend rejects the request through EXPORT_REGION_ACK.success = false, and the message + // controller turns an unsuccessful ack into a rejection carrying the whole ack. + const exportRegion = async ( + input: CARTA.IExportRegion, + regionStyles: Map + ): Promise => { + try { + return await msgController.exportRegion( + input.directory, + input.file, + input.type, + input.coordType, + input.fileId, + regionStyles, + input.overwrite + ); + } catch (err) { + return err as CARTA.IExportRegionAck; + } + }; + + describe(`Register a session`, () => { + beforeAll(async () => { + await msgController.connect(testServerUrl); + }, connectTimeout); + + checkConnection(); + + // The basepath is empty when the backend starts in its own top level folder, so only + // insert the separator when there is something to separate + const resolveDirectory = (subdirectory: string): string => + basepath ? basepath + '/' + subdirectory : subdirectory; + + test(`Get basepath and modify the directory path`, async () => { + let fileListResponse = await msgController.getFileList('$BASE', 0); + basepath = fileListResponse.directory; + assertItem.fileOpen.directory = resolveDirectory(assertItem.fileOpen.directory); + assertItem.exportRegion.map((exportRegionInput) => { + exportRegionInput.directory = resolveDirectory(exportRegionInput.directory); + }); + }); + + test( + `Open the image "${assertItem.fileOpen.file}"`, + async () => { + msgController.closeFile(-1); + let OpenFileResponse = await msgController.loadFile(assertItem.fileOpen); + await Stream(CARTA.RegionHistogramData, 1); + + expect(OpenFileResponse.success).toBe(true); + expect(OpenFileResponse.fileInfo.name).toEqual(assertItem.fileOpen.file); + }, + openFileTimeout + ); + + let regionStyles = new Map(); + test( + `Set a region to export`, + async () => { + let SetRegionAck = await msgController.setRegion( + assertItem.setRegion.fileId, + assertItem.setRegion.regionId, + assertItem.setRegion.regionInfo + ); + expect(SetRegionAck.success).toBe(true); + regionStyles.set(SetRegionAck.regionId, assertItem.regionStyle); + }, + regionTimeout + ); + + assertItem.exportRegion.map((exportRegionInput, index) => { + const expectedAck = assertItem.exportRegionAck[index]; + describe(`Export the regions to "${exportRegionInput.file}" with overwrite = ${exportRegionInput.overwrite}`, () => { + let exportRegionAck: CARTA.IExportRegionAck; + test( + `EXPORT_REGION_ACK should arrive within ${exportTimeout} ms`, + async () => { + exportRegionAck = await exportRegion(exportRegionInput, regionStyles); + }, + exportTimeout + ); + + test(`EXPORT_REGION_ACK.success = ${expectedAck.success}`, () => { + expect(exportRegionAck.success).toEqual(expectedAck.success); + }); + + test(`EXPORT_REGION_ACK.message = "${expectedAck.message}"`, () => { + expect(exportRegionAck.message).toEqual(expectedAck.message); + }); + + test(`EXPORT_REGION_ACK.overwrite_confirmation_required = ${expectedAck.overwriteConfirmationRequired}`, () => { + expect(exportRegionAck.overwriteConfirmationRequired).toEqual( + expectedAck.overwriteConfirmationRequired + ); + }); + }); + }); + + afterAll(() => msgController.closeConnection()); + }); +}); diff --git a/src/test/SAVE_IMAGE_OVERWRITE.test.ts b/src/test/SAVE_IMAGE_OVERWRITE.test.ts new file mode 100644 index 0000000..4bff818 --- /dev/null +++ b/src/test/SAVE_IMAGE_OVERWRITE.test.ts @@ -0,0 +1,319 @@ +import { CARTA } from 'carta-protobuf'; +import * as fs from 'fs'; +import * as path from 'path'; +import { checkConnection, Stream } from './MyClient'; +import { MessageController } from './MessageController'; +import config from './config.json'; + +let testServerUrl: string = config.serverURL0; +let testSubdirectory: string = config.path.QA; +let regionSubdirectory: string = config.path.region; +let saveSubdirectory: string = config.path.save; +// Absolute path of the backend top level folder, only used to create the symbolic link fixture. +// It must match the folder the backend was started with (see config.path.imageRoot). +let imageRoot: string = config.path.imageRoot; +let connectTimeout: number = config.timeout.connection; +let openFileTimeout: number = config.timeout.openFile; +let saveFileTimeout: number = config.timeout.saveFile; + +const symbolicLinkName: string = 'save_image_overwrite_symlink'; +const symbolicLinkTarget: string = 'M17_SWex_regionSet1_pix.crtf'; + +interface AssertItem { + fileOpen: CARTA.IOpenFile; + saveFile: CARTA.ISaveFile[]; + saveFileAck: CARTA.ISaveFileAck[]; +} + +let assertItem: AssertItem = { + fileOpen: { + directory: testSubdirectory, + file: 'M17_SWex.fits', + hdu: '', + fileId: 0, + renderMode: CARTA.RenderMode.RASTER, + }, + saveFile: [ + { + // No file name at all + fileId: 0, + outputFileDirectory: saveSubdirectory, + outputFileName: '', + outputFileType: CARTA.FileType.CASA, + regionId: 0, + channels: [], + stokes: [], + keepDegenerate: true, + restFreq: NaN, + overwrite: true, + }, + { + // The output path resolves to the image which is currently open + fileId: 0, + outputFileDirectory: testSubdirectory, + outputFileName: 'M17_SWex.fits', + outputFileType: CARTA.FileType.FITS, + regionId: 0, + channels: [], + stokes: [], + keepDegenerate: true, + restFreq: NaN, + overwrite: true, + }, + { + // The output path resolves to an existing directory which is not an image + fileId: 0, + outputFileDirectory: testSubdirectory, + outputFileName: 'regionTest', + outputFileType: CARTA.FileType.CASA, + regionId: 0, + channels: [], + stokes: [], + keepDegenerate: true, + restFreq: NaN, + overwrite: true, + }, + { + // The output path resolves to an existing file which is not an image + fileId: 0, + outputFileDirectory: regionSubdirectory, + outputFileName: symbolicLinkTarget, + outputFileType: CARTA.FileType.CASA, + regionId: 0, + channels: [], + stokes: [], + keepDegenerate: true, + restFreq: NaN, + overwrite: false, + }, + { + // The output path resolves to a symbolic link + fileId: 0, + outputFileDirectory: saveSubdirectory, + outputFileName: symbolicLinkName, + outputFileType: CARTA.FileType.CASA, + regionId: 0, + channels: [], + stokes: [], + keepDegenerate: true, + restFreq: NaN, + overwrite: false, + }, + { + // The output path resolves to an existing image file + fileId: 0, + outputFileDirectory: testSubdirectory, + outputFileName: 'M17_SWex.hdf5', + outputFileType: CARTA.FileType.HDF5, + regionId: 0, + channels: [], + stokes: [], + keepDegenerate: true, + restFreq: NaN, + overwrite: false, + }, + { + // The output path resolves to an existing image directory + fileId: 0, + outputFileDirectory: testSubdirectory, + outputFileName: 'M17_SWex.image', + outputFileType: CARTA.FileType.CASA, + regionId: 0, + channels: [], + stokes: [], + keepDegenerate: true, + restFreq: NaN, + overwrite: false, + }, + ], + saveFileAck: [ + { + fileId: 0, + success: false, + message: 'Cannot save image with no filename.', + overwriteConfirmationRequired: false, + }, + { + fileId: 0, + success: false, + message: 'Cannot overwrite the source image.', + overwriteConfirmationRequired: false, + }, + { + fileId: 0, + success: false, + message: 'Cannot overwrite existing directory.', + overwriteConfirmationRequired: false, + }, + { + fileId: 0, + success: false, + message: 'Cannot overwrite existing file or symlink.', + overwriteConfirmationRequired: true, + }, + { + fileId: 0, + success: false, + message: 'Cannot overwrite existing file or symlink.', + overwriteConfirmationRequired: true, + }, + { + fileId: 0, + success: false, + message: 'Cannot overwrite existing image.', + overwriteConfirmationRequired: true, + }, + { + fileId: 0, + success: false, + message: 'Cannot overwrite existing image.', + overwriteConfirmationRequired: true, + }, + ], +}; + +let basepath: string; +describe('SAVE_IMAGE_OVERWRITE: Saving an image to a path which cannot be overwritten', () => { + const msgController = MessageController.Instance; + + // The backend rejects the request through SAVE_FILE_ACK.success = false, and the message + // controller turns an unsuccessful ack into a rejection carrying the whole ack. + const saveFile = async (input: CARTA.ISaveFile): Promise => { + try { + return await msgController.saveFile( + input.fileId, + input.outputFileDirectory, + input.outputFileName, + input.outputFileType, + input.regionId, + input.channels, + input.stokes, + input.keepDegenerate, + input.restFreq, + input.overwrite + ); + } catch (err) { + return err as CARTA.ISaveFileAck; + } + }; + + const linkDirectory = path.join(imageRoot, saveSubdirectory); + const linkPath = path.join(linkDirectory, symbolicLinkName); + + // The ICD tests run on whichever node the runner provides, which is still node 12 on some of + // them, so stay with file system calls which have been available all along: lstatSync has no + // throwIfNoEntry option before node 14.17 and rmSync does not exist before node 14.14. + const lstatOrNull = (target: string): fs.Stats | null => { + try { + return fs.lstatSync(target); + } catch (err) { + return null; + } + }; + + const removePath = (target: string): void => { + const stats = lstatOrNull(target); + if (!stats) { + return; + } + if (stats.isDirectory()) { + fs.readdirSync(target).forEach((entry) => removePath(path.join(target, entry))); + fs.rmdirSync(target); + } else { + fs.unlinkSync(target); + } + }; + + describe(`Register a session`, () => { + // The symbolic link must exist before any request is sent: with nothing at the target path + // the backend has nothing to refuse and saves a new image there instead. Preparing it in + // beforeAll aborts the whole block on failure, so the checks below can never write a file. + beforeAll(() => { + if (!fs.existsSync(imageRoot)) { + throw new Error( + `config.path.imageRoot "${imageRoot}" does not exist, set it to the top level folder of the backend.` + ); + } + const linkTarget = path.join(imageRoot, regionSubdirectory, symbolicLinkTarget); + if (!fs.existsSync(linkTarget)) { + throw new Error(`The symbolic link target "${linkTarget}" does not exist.`); + } + fs.mkdirSync(linkDirectory, { recursive: true }); + // Remove anything left behind by an interrupted run, which may be a file or a directory + removePath(linkPath); + fs.symlinkSync(linkTarget, linkPath); + expect(fs.lstatSync(linkPath).isSymbolicLink()).toBe(true); + }); + + beforeAll(async () => { + await msgController.connect(testServerUrl); + }, connectTimeout); + + checkConnection(); + + // The basepath is empty when the backend starts in its own top level folder, and prefixing + // a subdirectory with "basepath + '/'" would then make it absolute. SaveFile joins the + // directory with std::filesystem::operator/, which drops the top level folder for an + // absolute directory, so the request would leave the image folder entirely. + const resolveDirectory = (subdirectory: string): string => + basepath ? basepath + '/' + subdirectory : subdirectory; + + test(`Get basepath and modify the directory path`, async () => { + let fileListResponse = await msgController.getFileList('$BASE', 0); + basepath = fileListResponse.directory; + assertItem.fileOpen.directory = resolveDirectory(assertItem.fileOpen.directory); + assertItem.saveFile.map((saveFileInput) => { + saveFileInput.outputFileDirectory = resolveDirectory(saveFileInput.outputFileDirectory); + }); + }); + + test( + `Open the image "${assertItem.fileOpen.file}"`, + async () => { + msgController.closeFile(-1); + let OpenFileResponse = await msgController.loadFile(assertItem.fileOpen); + await Stream(CARTA.RegionHistogramData, 1); + + expect(OpenFileResponse.success).toBe(true); + expect(OpenFileResponse.fileInfo.name).toEqual(assertItem.fileOpen.file); + }, + openFileTimeout + ); + + assertItem.saveFile.map((saveFileInput, index) => { + const expectedAck = assertItem.saveFileAck[index]; + describe(`Save the image to "${saveFileInput.outputFileName === '' ? '(no file name)' : saveFileInput.outputFileName}" with overwrite = ${saveFileInput.overwrite}`, () => { + let saveFileAck: CARTA.ISaveFileAck; + test( + `SAVE_FILE_ACK should arrive within ${saveFileTimeout} ms`, + async () => { + saveFileAck = await saveFile(saveFileInput); + }, + saveFileTimeout + ); + + test(`SAVE_FILE_ACK.success = ${expectedAck.success}`, () => { + expect(saveFileAck.success).toEqual(expectedAck.success); + }); + + test(`SAVE_FILE_ACK.message = "${expectedAck.message}"`, () => { + expect(saveFileAck.message).toEqual(expectedAck.message); + }); + + test(`SAVE_FILE_ACK.overwrite_confirmation_required = ${expectedAck.overwriteConfirmationRequired}`, () => { + expect(saveFileAck.overwriteConfirmationRequired).toEqual( + expectedAck.overwriteConfirmationRequired + ); + }); + }); + }); + + afterAll(() => { + const stats = lstatOrNull(linkPath); + if (stats && stats.isSymbolicLink()) { + fs.unlinkSync(linkPath); + } + msgController.closeConnection(); + }); + }); +}); diff --git a/src/test/config.json b/src/test/config.json index 1902afa..43b5d63 100644 --- a/src/test/config.json +++ b/src/test/config.json @@ -4,6 +4,7 @@ "path": { "root": ".", "base": "$BASE", + "imageRoot": "/images", "directory": ".", "QA": "set_QA", "casa_varients": "set_casaImageVarients",