From a3b01c2dd24ccf1d2a4fa8c89df7c4c861fc52ac Mon Sep 17 00:00:00 2001 From: 0xCmdrKeen <98132670+0xCmdrKeen@users.noreply.github.com> Date: Tue, 17 Oct 2023 11:58:51 -0700 Subject: Fix bugs in data import from Ferdium app (#82) * Fixed misspelled field names * Fixed broken tests * Ensure service.settings and workspace.data are not JSON encoded twice * Accept both snake_case and camelCase input files * More tests for JSON fields * Add filename to assertion messages --- .../Http/Dashboard/TransferController.ts | 15 +- .../import-stubs/services-only.ferdium-data | 24 +- .../dashboard/import-stubs/services-only.json | 6 +- .../import-stubs/services-workspaces.ferdium-data | 30 +- .../import-stubs/services-workspaces.json | 6 +- .../import-stubs/workspaces-only.ferdium-data | 6 +- tests/functional/dashboard/transfer.spec.ts | 414 +++++++++++---------- 7 files changed, 261 insertions(+), 240 deletions(-) diff --git a/app/Controllers/Http/Dashboard/TransferController.ts b/app/Controllers/Http/Dashboard/TransferController.ts index a005c1b..b113e50 100644 --- a/app/Controllers/Http/Dashboard/TransferController.ts +++ b/app/Controllers/Http/Dashboard/TransferController.ts @@ -68,12 +68,16 @@ export default class TransferController { userId: auth.user?.id, serviceId, name: service.name, - recipeId: service.recipe_id, - settings: JSON.stringify(service.settings), + recipeId: service.recipe_id || service.recipeId, + settings: + typeof service.settings === 'string' + ? service.settings + : JSON.stringify(service.settings), }); // @ts-expect-error Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{}' - serviceIdTranslation[service.service_id] = serviceId; + serviceIdTranslation[service.service_id || service.serviceId] = + serviceId; } } catch (error) { // eslint-disable-next-line no-console @@ -109,7 +113,10 @@ export default class TransferController { name: workspace.name, order: workspace.order, services: JSON.stringify(services), - data: JSON.stringify(workspace.data), + data: + typeof workspace.data === 'string' + ? workspace.data + : JSON.stringify(workspace.data), }); } } catch (error) { diff --git a/tests/functional/dashboard/import-stubs/services-only.ferdium-data b/tests/functional/dashboard/import-stubs/services-only.ferdium-data index d95f3ef..cd3f381 100644 --- a/tests/functional/dashboard/import-stubs/services-only.ferdium-data +++ b/tests/functional/dashboard/import-stubs/services-only.ferdium-data @@ -5,31 +5,31 @@ "services": [ { "id": 5641, - "userId": "1234", - "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b", + "user_id": "1234", + "service_id": "d6901fff-ec44-4251-93de-d7103ed9c44b", "name": "random-service-1", - "recipeId": "random-service-1", - "settings": "{}", + "recipe_id": "random-service-1", + "settings": {"isEnabled": true}, "created_at": "2022-06-21 08:29:13", "updated_at": "2022-07-19 15:47:16" }, { "id": 2134, - "userId": "1234", - "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b", + "user_id": "1234", + "service_id": "d6901fff-ec44-4251-93de-d7103ed9c44b", "name": "random-service-1", - "recipeId": "random-service-1", - "settings": "{}", + "recipe_id": "random-service-1", + "settings": {"isEnabled": true}, "created_at": "2022-06-21 08:29:13", "updated_at": "2022-07-19 15:47:16" }, { "id": 5343, - "userId": "1234", - "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b", + "user_id": "1234", + "service_id": "d6901fff-ec44-4251-93de-d7103ed9c44b", "name": "random-service-1", - "recipeId": "random-service-1", - "settings": "{}", + "recipe_id": "random-service-1", + "settings": {"isEnabled": true}, "created_at": "2022-06-21 08:29:13", "updated_at": "2022-07-19 15:47:16" } diff --git a/tests/functional/dashboard/import-stubs/services-only.json b/tests/functional/dashboard/import-stubs/services-only.json index d95f3ef..c4a9147 100644 --- a/tests/functional/dashboard/import-stubs/services-only.json +++ b/tests/functional/dashboard/import-stubs/services-only.json @@ -9,7 +9,7 @@ "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b", "name": "random-service-1", "recipeId": "random-service-1", - "settings": "{}", + "settings": "{\"isEnabled\":true}", "created_at": "2022-06-21 08:29:13", "updated_at": "2022-07-19 15:47:16" }, @@ -19,7 +19,7 @@ "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b", "name": "random-service-1", "recipeId": "random-service-1", - "settings": "{}", + "settings": "{\"isEnabled\":true}", "created_at": "2022-06-21 08:29:13", "updated_at": "2022-07-19 15:47:16" }, @@ -29,7 +29,7 @@ "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b", "name": "random-service-1", "recipeId": "random-service-1", - "settings": "{}", + "settings": "{\"isEnabled\":true}", "created_at": "2022-06-21 08:29:13", "updated_at": "2022-07-19 15:47:16" } diff --git a/tests/functional/dashboard/import-stubs/services-workspaces.ferdium-data b/tests/functional/dashboard/import-stubs/services-workspaces.ferdium-data index e999c0d..1d90f4a 100644 --- a/tests/functional/dashboard/import-stubs/services-workspaces.ferdium-data +++ b/tests/functional/dashboard/import-stubs/services-workspaces.ferdium-data @@ -5,31 +5,31 @@ "services": [ { "id": 5641, - "userId": "1234", - "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b", + "user_id": "1234", + "service_id": "d6901fff-ec44-4251-93de-d7103ed9c44b", "name": "random-service-1", - "recipeId": "random-service-1", - "settings": "{}", + "recipe_id": "random-service-1", + "settings": {"isEnabled": true}, "created_at": "2022-06-21 08:29:13", "updated_at": "2022-07-19 15:47:16" }, { "id": 2134, - "userId": "1234", - "serviceId": "79769de5-a998-4af1-b7d0-89956a15b0ed", + "user_id": "1234", + "service_id": "79769de5-a998-4af1-b7d0-89956a15b0ed", "name": "random-service-2", - "recipeId": "random-service-2", - "settings": "{}", + "recipe_id": "random-service-2", + "settings": {"isEnabled": true}, "created_at": "2022-06-21 08:29:13", "updated_at": "2022-07-19 15:47:16" }, { "id": 5343, - "userId": "1234", - "serviceId": "0ac973f8-40dc-4760-b2c2-55e1d2943747", + "user_id": "1234", + "service_id": "0ac973f8-40dc-4760-b2c2-55e1d2943747", "name": "random-service-3", - "recipeId": "random-service-2", - "settings": "{}", + "recipe_id": "random-service-2", + "settings": {"isEnabled": true}, "created_at": "2022-06-21 08:29:13", "updated_at": "2022-07-19 15:47:16" } @@ -39,13 +39,13 @@ "name": "workspace1", "order": 0, "services": [], - "data": "{\"name\":\"workspace1\"}" + "data": {"name":"workspace1"} }, { "name": "workspace2", "order": 0, "services": ["d6901fff-ec44-4251-93de-d7103ed9c44b", "79769de5-a998-4af1-b7d0-89956a15b0ed"], - "data": "{\"name\":\"workspace2\"}" + "data": {"name":"workspace2"} }, { "name": "workspace3", @@ -55,7 +55,7 @@ "79769de5-a998-4af1-b7d0-89956a15b0ed", "0ac973f8-40dc-4760-b2c2-55e1d2943747" ], - "data": "{\"name\":\"workspace3\"}" + "data": {"name":"workspace3"} } ] } diff --git a/tests/functional/dashboard/import-stubs/services-workspaces.json b/tests/functional/dashboard/import-stubs/services-workspaces.json index 54c6889..139b32c 100644 --- a/tests/functional/dashboard/import-stubs/services-workspaces.json +++ b/tests/functional/dashboard/import-stubs/services-workspaces.json @@ -9,7 +9,7 @@ "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b", "name": "random-service-1", "recipeId": "random-service-1", - "settings": "{}", + "settings": "{\"isEnabled\":true}", "created_at": "2022-06-21 08:29:13", "updated_at": "2022-07-19 15:47:16" }, @@ -19,7 +19,7 @@ "serviceId": "79769de5-a998-4af1-b7d0-89956a15b0ed", "name": "random-service-2", "recipeId": "random-service-2", - "settings": "{}", + "settings": "{\"isEnabled\":true}", "created_at": "2022-06-21 08:29:13", "updated_at": "2022-07-19 15:47:16" }, @@ -29,7 +29,7 @@ "serviceId": "0ac973f8-40dc-4760-b2c2-55e1d2943747", "name": "random-service-3", "recipeId": "random-service-3", - "settings": "{}", + "settings": "{\"isEnabled\":true}", "created_at": "2022-06-21 08:29:13", "updated_at": "2022-07-19 15:47:16" } diff --git a/tests/functional/dashboard/import-stubs/workspaces-only.ferdium-data b/tests/functional/dashboard/import-stubs/workspaces-only.ferdium-data index 13ea9c6..cf0fca0 100644 --- a/tests/functional/dashboard/import-stubs/workspaces-only.ferdium-data +++ b/tests/functional/dashboard/import-stubs/workspaces-only.ferdium-data @@ -8,19 +8,19 @@ "name": "workspace1", "order": 0, "services": [], - "data": "{\"name\":\"workspace1\"}" + "data": {"name":"workspace1"} }, { "name": "workspace2", "order": 0, "services": [], - "data": "{\"name\":\"workspace2\"}" + "data": {"name":"workspace2"} }, { "name": "workspace3", "order": 0, "services": [], - "data": "{\"name\":\"workspace3\"}" + "data": {"name":"workspace3"} } ] } diff --git a/tests/functional/dashboard/transfer.spec.ts b/tests/functional/dashboard/transfer.spec.ts index 0f8ee31..e40fca2 100644 --- a/tests/functional/dashboard/transfer.spec.ts +++ b/tests/functional/dashboard/transfer.spec.ts @@ -1,4 +1,5 @@ import { test } from '@japa/runner'; +import { readFileSync } from 'node:fs'; import UserFactory from 'Database/factories/UserFactory'; test.group('Dashboard / Transfer page', () => { @@ -19,204 +20,217 @@ test.group('Dashboard / Transfer page', () => { response.assertStatus(200); }); - // TODO: Fix the following tests - - // test('returns a validation error when not uploading a file', async ({ - // client, - // }) => { - // const user = await UserFactory.create(); - // const response = await client.put('/user/transfer').loginAs(user); - - // response.assertTextIncludes('File missing'); - // }); - - // test('returns a validation error when trying to use anything but json', async ({ - // client, - // }) => { - // const user = await UserFactory.create(); - // const response = await client - // .put('/user/transfer') - // .loginAs(user) - // .file('file', 'tests/functional/dashboard/import-stubs/random-file.txt', { - // filename: 'random-file.txt', - // }); - - // response.assertTextIncludes('File missing'); - // }); - - // test('returns a validation error when trying to use anything valid json', async ({ - // client, - // }) => { - // const user = await UserFactory.create(); - // const response = await client - // .put('/user/transfer') - // .loginAs(user) - // .file('file', 'tests/functional/dashboard/import-stubs/invalid.json', { - // filename: 'invalid.json', - // }); - - // response.assertTextIncludes('Invalid Ferdium account file'); - // }); - - // test('returns a validation error when trying to use a json file with no ferdium structure', async ({ - // client, - // }) => { - // const user = await UserFactory.create(); - // const response = await client - // .put('/user/transfer') - // .loginAs(user) - // .file( - // 'file', - // 'tests/functional/dashboard/import-stubs/valid-no-data.json', - // { - // filename: 'valid-no-data.json', - // }, - // ); - - // response.assertTextIncludes('Invalid Ferdium account file (2)'); - // }); - - // test('correctly transfers data from json/ferdi-data and ferdium-data file with only workspaces', async ({ - // client, - // assert, - // }) => { - // // Repeat for all 3 file extension - // const files = [ - // 'workspaces-only.json', - // 'workspaces-only.ferdi-data', - // 'workspaces-only.ferdium-data', - // ]; - - // for (const file of files) { - // // eslint-disable-next-line no-await-in-loop - // const user = await UserFactory.create(); - // // eslint-disable-next-line no-await-in-loop - // const response = await client - // .put('/user/transfer') - // .loginAs(user) - // .file('file', `tests/functional/dashboard/import-stubs/${file}`, { - // filename: file, - // }); - - // response.assertTextIncludes( - // 'Your account has been imported, you can now login as usual!', - // ); - // // eslint-disable-next-line no-await-in-loop - // await user.refresh(); - - // // eslint-disable-next-line no-await-in-loop - // const workspacesForUser = await user.related('workspaces').query(); - // assert.equal(workspacesForUser.length, 3); - // } - // }); - - // test('correctly transfers data from json/ferdi-data and ferdium-data file with only services', async ({ - // client, - // assert, - // }) => { - // // Repeat for all 3 file extension - // const files = [ - // 'services-only.json', - // 'services-only.ferdi-data', - // 'services-only.ferdium-data', - // ]; - - // for (const file of files) { - // // eslint-disable-next-line no-await-in-loop - // const user = await UserFactory.create(); - // // eslint-disable-next-line no-await-in-loop - // const response = await client - // .put('/user/transfer') - // .loginAs(user) - // .file('file', `tests/functional/dashboard/import-stubs/${file}`, { - // filename: file, - // }); - - // response.assertTextIncludes( - // 'Your account has been imported, you can now login as usual!', - // ); - // // eslint-disable-next-line no-await-in-loop - // await user.refresh(); - - // // eslint-disable-next-line no-await-in-loop - // const servicesForUser = await user.related('services').query(); - // assert.equal(servicesForUser.length, 3); - // } - // }); - - // test('correctly transfers data from json/ferdi-data and ferdium-data file with workspaces and services', async ({ - // client, - // assert, - // }) => { - // // Repeat for all 3 file extension - // const files = [ - // 'services-workspaces.json', - // 'services-workspaces.ferdi-data', - // 'services-workspaces.ferdium-data', - // ]; - - // for (const file of files) { - // // eslint-disable-next-line no-await-in-loop - // const user = await UserFactory.create(); - // // eslint-disable-next-line no-await-in-loop - // const response = await client - // .put('/user/transfer') - // .loginAs(user) - // .file('file', `tests/functional/dashboard/import-stubs/${file}`, { - // filename: file, - // }); - - // response.assertTextIncludes( - // 'Your account has been imported, you can now login as usual!', - // ); - // // eslint-disable-next-line no-await-in-loop - // await user.refresh(); - - // // eslint-disable-next-line no-await-in-loop - // const servicesForUser = await user.related('services').query(); - // // eslint-disable-next-line no-await-in-loop - // const workspacesForUser = await user.related('workspaces').query(); - // assert.equal(servicesForUser.length, 3); - // assert.equal(workspacesForUser.length, 3); - - // const firstServiceUuid = servicesForUser.find( - // s => s.name === 'random-service-1', - // )?.serviceId; - // const secondServiceUuid = servicesForUser.find( - // s => s.name === 'random-service-2', - // )?.serviceId; - // const thirdServiceUUid = servicesForUser.find( - // s => s.name === 'random-service-3', - // )?.serviceId; - - // // Assert the first workspace to not have any services - // const firstWorkspace = workspacesForUser.find( - // w => w.name === 'workspace1', - // ); - // if (firstWorkspace?.services) { - // assert.empty(JSON.parse(firstWorkspace.services)); - // } - - // const secondWorkspace = workspacesForUser.find( - // w => w.name === 'workspace2', - // ); - // if (secondWorkspace?.services) { - // assert.deepEqual(JSON.parse(secondWorkspace.services), [ - // firstServiceUuid, - // secondServiceUuid, - // ]); - // } - - // const thirdWorkspace = workspacesForUser.find( - // w => w.name === 'workspace3', - // ); - // if (thirdWorkspace?.services) { - // assert.deepEqual(JSON.parse(thirdWorkspace.services), [ - // firstServiceUuid, - // secondServiceUuid, - // thirdServiceUUid, - // ]); - // } - // } - // }); + test('returns a validation error when not uploading a file', async ({ + client, + }) => { + const user = await UserFactory.create(); + const response = await client.post('/user/transfer').loginAs(user); + + response.assertTextIncludes('Invalid Ferdium account file'); + }); + + test('returns a validation error when trying to use anything but json', async ({ + client, + }) => { + const user = await UserFactory.create(); + const response = await client + .post('/user/transfer') + .loginAs(user) + .field( + 'file', + readFileSync('tests/functional/dashboard/import-stubs/random-file.txt'), + ); + + response.assertTextIncludes('Invalid Ferdium account file'); + }); + + test('returns a validation error when trying to use anything valid json', async ({ + client, + }) => { + const user = await UserFactory.create(); + const response = await client + .post('/user/transfer') + .loginAs(user) + .field( + 'file', + readFileSync('tests/functional/dashboard/import-stubs/invalid.json'), + ); + + response.assertTextIncludes('Invalid Ferdium account file'); + }); + + test('returns a validation error when trying to use a json file with no ferdium structure', async ({ + client, + }) => { + const user = await UserFactory.create(); + const response = await client + .post('/user/transfer') + .loginAs(user) + .field( + 'file', + readFileSync( + 'tests/functional/dashboard/import-stubs/valid-no-data.json', + ), + ); + + response.assertTextIncludes('Invalid Ferdium account file'); + }); + + test('correctly transfers data from json/ferdi-data and ferdium-data file with only workspaces', async ({ + client, + assert, + }) => { + // Repeat for all 3 file extension + const files = [ + 'workspaces-only.json', + 'workspaces-only.ferdi-data', + 'workspaces-only.ferdium-data', + ]; + + for (const file of files) { + // eslint-disable-next-line no-await-in-loop + const user = await UserFactory.create(); + // eslint-disable-next-line no-await-in-loop + const response = await client + .post('/user/transfer') + .loginAs(user) + .field( + 'file', + readFileSync(`tests/functional/dashboard/import-stubs/${file}`), + ); + + response.assertTextIncludes( + 'Your account has been imported, you can now login as usual!', + ); + // eslint-disable-next-line no-await-in-loop + await user.refresh(); + + // eslint-disable-next-line no-await-in-loop + const workspacesForUser = await user.related('workspaces').query(); + assert.equal(workspacesForUser.length, 3, file); + + // ensure not JSON encoded twice + for (const workspace of workspacesForUser) { + assert.isNull(workspace.data.match(/\\"/), file); + } + } + }); + + test('correctly transfers data from json/ferdi-data and ferdium-data file with only services', async ({ + client, + assert, + }) => { + // Repeat for all 3 file extension + const files = [ + 'services-only.json', + 'services-only.ferdi-data', + 'services-only.ferdium-data', + ]; + + for (const file of files) { + // eslint-disable-next-line no-await-in-loop + const user = await UserFactory.create(); + // eslint-disable-next-line no-await-in-loop + const response = await client + .post('/user/transfer') + .loginAs(user) + .field( + 'file', + readFileSync(`tests/functional/dashboard/import-stubs/${file}`), + ); + + response.assertTextIncludes( + 'Your account has been imported, you can now login as usual!', + ); + // eslint-disable-next-line no-await-in-loop + await user.refresh(); + + // eslint-disable-next-line no-await-in-loop + const servicesForUser = await user.related('services').query(); + assert.equal(servicesForUser.length, 3, file); + + // ensure not JSON encoded twice + for (const service of servicesForUser) { + assert.isNull(service.settings.match(/\\"/), file); + } + } + }); + + test('correctly transfers data from json/ferdi-data and ferdium-data file with workspaces and services', async ({ + client, + assert, + }) => { + // Repeat for all 3 file extension + const files = [ + 'services-workspaces.json', + 'services-workspaces.ferdi-data', + 'services-workspaces.ferdium-data', + ]; + + for (const file of files) { + // eslint-disable-next-line no-await-in-loop + const user = await UserFactory.create(); + // eslint-disable-next-line no-await-in-loop + const response = await client + .post('/user/transfer') + .loginAs(user) + .field( + 'file', + readFileSync(`tests/functional/dashboard/import-stubs/${file}`), + ); + + response.assertTextIncludes( + 'Your account has been imported, you can now login as usual!', + ); + // eslint-disable-next-line no-await-in-loop + await user.refresh(); + + // eslint-disable-next-line no-await-in-loop + const servicesForUser = await user.related('services').query(); + // eslint-disable-next-line no-await-in-loop + const workspacesForUser = await user.related('workspaces').query(); + assert.equal(servicesForUser.length, 3, file); + assert.equal(workspacesForUser.length, 3, file); + + const firstServiceUuid = servicesForUser.find( + s => s.name === 'random-service-1', + )?.serviceId; + const secondServiceUuid = servicesForUser.find( + s => s.name === 'random-service-2', + )?.serviceId; + const thirdServiceUUid = servicesForUser.find( + s => s.name === 'random-service-3', + )?.serviceId; + + // Assert the first workspace to not have any services + const firstWorkspace = workspacesForUser.find( + w => w.name === 'workspace1', + ); + if (firstWorkspace?.services) { + assert.empty(JSON.parse(firstWorkspace.services), file); + } + + const secondWorkspace = workspacesForUser.find( + w => w.name === 'workspace2', + ); + if (secondWorkspace?.services) { + assert.deepEqual( + JSON.parse(secondWorkspace.services), + [firstServiceUuid, secondServiceUuid], + file, + ); + } + + const thirdWorkspace = workspacesForUser.find( + w => w.name === 'workspace3', + ); + if (thirdWorkspace?.services) { + assert.deepEqual( + JSON.parse(thirdWorkspace.services), + [firstServiceUuid, secondServiceUuid, thirdServiceUUid], + file, + ); + } + } + }); }); -- cgit v1.2.3-54-g00ecf