aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorLibravatar Ricardo <ricardo@cino.io>2023-10-13 14:12:03 +0200
committerLibravatar GitHub <noreply@github.com>2023-10-13 13:12:03 +0100
commite503468660a13760010a94ecda5f0625c6f47f87 (patch)
treefa532f54fc5f091de08d55405ec6339bd2440a02 /tests
parent1.3.16 [skip ci] (diff)
downloadferdium-server-e503468660a13760010a94ecda5f0625c6f47f87.tar.gz
ferdium-server-e503468660a13760010a94ecda5f0625c6f47f87.tar.zst
ferdium-server-e503468660a13760010a94ecda5f0625c6f47f87.zip
Server re-build with latest AdonisJS framework & Typescript (#47)
* chore: setup first basis structure * chore: ensure styling is loaded correctly * chore: comply to new routing syntax by replace . with / in routes/resource locations * chore: add login controller * chore: correctly use views with slash instead of dot * chore: working login + tests * chore: clean up tests * chore: add password-forgot endpoint and matching test * chore: add delete page test * chore: add logout test * chore: add reset-password route and tests * chore: remove obsolete comment * chore: add account-page and tests * chore: add data page & first step of the test * chore: add transfer/import data feature and tests * chore: add export and basic test * chore: add all static api routes with tests * Regenerate 'pnpm-lock.json' and fix bad merge conflict WIP: - Tests have been commented out since they dont work - Server doesn't start * easier dev and test runs * - remove --require-pragma from reformat-files so formatting works properly - run pnpm reformat-files over codebase - remove .json files from .eslintignore - add invalid.json file to .eslintignore - configure prettier properly in eslint config - add type jsdoc to prettier config - run adonis generate:manifest command to regenerate ace-manifest.json - specify volta in package.json - introduce typecheck npm script - remove unused .mjs extension from npm scripts - install missing type definition dependencies - add pnpm.allowedDeprecatedVersions to package.json - fix invalid extends in tsconfig.json causing TS issues throughout codebase - remove @ts-ignore throughout codebase which is not relevant anymore - enable some of the tsconfig options - remove outdated eslint-disable from codebase - change deprecated faker.company.companyName() to faker.company.name() - fix TS issues inside transfer.spec.ts * - update to latest node and pnpm versions - upgrade all non-major dependencies to latest - install missing @types/luxon dependency - add cuid to pnpm.allowedDeprecatedVersions - add esModuleInterop config option to tsconfig - migrate more deprecated faker methods to new ones - add more temporary ts-ignore to code * - update eslint config - remove trailingComma: all since default in prettier v3 - add typecheck command to prepare-code npm script - upgrade various dependencies to latest major version - update tsconfig to include only useful config options - disable some lint issues and fix others * - add test command to prepare-code - disable strictPropertyInitialization flag in tsconfig which creates issues with adonis models - update precommit hook to excute pnpm prepare-code - remove ts-ignore statements from all models * fix node and pnpm dependency update * add cross env (so that we can develop on windows) * add signup endpoint (TODO: JWT auth) * Add login endpoint * Add me and updateMe endpoints * Add service endpoint * refactor: change endpoints to use jwt * add recipes endpoint * add workspaces endpoint * fix web controllors for login and post import * Update node deps * Change auth middleware (for web) and exempt api from CSRF * Add import endpoint (franz import) * Fix export/import logic * Fix service and workspace data in user/data * Fix partial lint * chore: workaround lint issues * fix: migration naming had two . * Sync back node with recipes repo * Temporarily ignore typescript * Fix adonisrc to handle public folder static assets * Fix issue with production database * add Legacy Password Provider * Fix lint errors * Fix issue on login errors frontend * add Legacy Password Provider * Fix issue with customIcons * Fix issue with auth tokens * Update 'node' to '18.18.0' * make docker work * improve docker entrypoint (test api performance) * Add migration database script * NODE_ENV on recipes * prefer @ts-expect-error over @ts-ignore * small fixes * Update 'pnpm' to '8.7.6' * fix error catch * Automatically generate JWT Public and Private keys * Use custom Adonis5-jwt * Update code to use secret (old way, no breaking changes) * Normalize appKey * Trick to make JWT tokens on client work with new version * Fix error with new JWT logic * Change migration and how we store JWT * Fix 500 response code (needs to be 401) * Improve logic and fix bugs * Fix build and entrypoint logic * Catch error if appKey changes * Add newToken logic * Fix lint (ignore any errors) * Add build for PRs * pnpm reformat-files result * Fix some tests * Fix reset password not working (test failing) * Restore csrfTokens (disabled by accident) * Fix pnpm start command with .env * Disable failing tests on the transfer endpoint (TODO) * Add tests to PR build * Fix build * Remove unnecessary assertStatus * Add typecheck * hash password on UserFactory (fix build) * Add JWT_USE_PEM true by default (increase security) * fix name of github action --------- Co-authored-by: Vijay A <vraravam@users.noreply.github.com> Co-authored-by: Balaji Vijayakumar <kuttibalaji.v6@gmail.com> Co-authored-by: MCMXC <16797721+mcmxcdev@users.noreply.github.com> Co-authored-by: André Oliveira <oliveira.andrerodrigues95@gmail.com>
Diffstat (limited to 'tests')
-rw-r--r--tests/bootstrap.ts83
-rw-r--r--tests/config.ts1
-rw-r--r--tests/functional/api/register.spec.ts0
-rw-r--r--tests/functional/api/static/announcements.spec.ts55
-rw-r--r--tests/functional/api/static/features.spec.ts29
-rw-r--r--tests/functional/api/static/news.spec.ts11
-rw-r--r--tests/functional/api/static/services.spec.ts11
-rw-r--r--tests/functional/dashboard/account.spec.ts124
-rw-r--r--tests/functional/dashboard/data.spec.ts31
-rw-r--r--tests/functional/dashboard/delete.spec.ts37
-rw-r--r--tests/functional/dashboard/disabled-dashboard.spec.ts71
-rw-r--r--tests/functional/dashboard/export.spec.ts61
-rw-r--r--tests/functional/dashboard/forgot-password.spec.ts70
-rw-r--r--tests/functional/dashboard/import-stubs/invalid.json1
-rw-r--r--tests/functional/dashboard/import-stubs/random-file.txt0
-rw-r--r--tests/functional/dashboard/import-stubs/services-only.ferdi-data38
-rw-r--r--tests/functional/dashboard/import-stubs/services-only.ferdium-data38
-rw-r--r--tests/functional/dashboard/import-stubs/services-only.json38
-rw-r--r--tests/functional/dashboard/import-stubs/services-workspaces.ferdi-data61
-rw-r--r--tests/functional/dashboard/import-stubs/services-workspaces.ferdium-data61
-rw-r--r--tests/functional/dashboard/import-stubs/services-workspaces.json64
-rw-r--r--tests/functional/dashboard/import-stubs/valid-no-data.json3
-rw-r--r--tests/functional/dashboard/import-stubs/workspaces-only.ferdi-data26
-rw-r--r--tests/functional/dashboard/import-stubs/workspaces-only.ferdium-data26
-rw-r--r--tests/functional/dashboard/import-stubs/workspaces-only.json26
-rw-r--r--tests/functional/dashboard/login.spec.ts65
-rw-r--r--tests/functional/dashboard/logout.spec.ts21
-rw-r--r--tests/functional/dashboard/reset-password.spec.ts122
-rw-r--r--tests/functional/dashboard/transfer.spec.ts222
-rw-r--r--tests/functional/health.spec.ts13
-rw-r--r--tests/functional/static-pages/home.spec.ts10
-rw-r--r--tests/functional/static-pages/privacy.spec.ts10
-rw-r--r--tests/functional/static-pages/terms.spec.ts10
-rw-r--r--tests/utils.ts6
34 files changed, 1445 insertions, 0 deletions
diff --git a/tests/bootstrap.ts b/tests/bootstrap.ts
new file mode 100644
index 0000000..efd1d1f
--- /dev/null
+++ b/tests/bootstrap.ts
@@ -0,0 +1,83 @@
1/**
2 * File source: https://bit.ly/3ukaHTz
3 *
4 * Feel free to let us know via PR, if you find something broken in this contract
5 * file.
6 */
7
8import type { Config } from '@japa/runner';
9import TestUtils from '@ioc:Adonis/Core/TestUtils';
10import {
11 assert,
12 runFailedTests,
13 specReporter,
14 apiClient,
15} from '@japa/preset-adonis';
16import { fakeCsrfField } from './utils';
17
18/*
19|--------------------------------------------------------------------------
20| Japa Plugins
21|--------------------------------------------------------------------------
22|
23| Japa plugins allows you to add additional features to Japa. By default
24| we register the assertion plugin.
25|
26| Feel free to remove existing plugins or add more.
27|
28*/
29export const plugins: Config['plugins'] = [
30 assert(),
31 runFailedTests(),
32 apiClient(),
33];
34
35/*
36|--------------------------------------------------------------------------
37| Japa Reporters
38|--------------------------------------------------------------------------
39|
40| Japa reporters displays/saves the progress of tests as they are executed.
41| By default, we register the spec reporter to show a detailed report
42| of tests on the terminal.
43|
44*/
45export const reporters: Config['reporters'] = [specReporter()];
46
47/*
48|--------------------------------------------------------------------------
49| Runner hooks
50|--------------------------------------------------------------------------
51|
52| Runner hooks are executed after booting the AdonisJS app and
53| before the test files are imported.
54|
55| You can perform actions like starting the HTTP server or running migrations
56| within the runner hooks
57|
58*/
59export const runnerHooks: Required<Pick<Config, 'setup' | 'teardown'>> = {
60 setup: [
61 () => TestUtils.ace().loadCommands(),
62 () => TestUtils.db().migrate(),
63 () => fakeCsrfField(),
64 ],
65 teardown: [],
66};
67
68/*
69|--------------------------------------------------------------------------
70| Configure individual suites
71|--------------------------------------------------------------------------
72|
73| The configureSuite method gets called for every test suite registered
74| within ".adonisrc.json" file.
75|
76| You can use this method to configure suites. For example: Only start
77| the HTTP server when it is a functional suite.
78*/
79export const configureSuite: Config['configureSuite'] = suite => {
80 if (suite.name === 'functional') {
81 suite.setup(() => TestUtils.httpServer().start());
82 }
83};
diff --git a/tests/config.ts b/tests/config.ts
new file mode 100644
index 0000000..8cd3511
--- /dev/null
+++ b/tests/config.ts
@@ -0,0 +1 @@
export const apiVersion = 'v1';
diff --git a/tests/functional/api/register.spec.ts b/tests/functional/api/register.spec.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/functional/api/register.spec.ts
diff --git a/tests/functional/api/static/announcements.spec.ts b/tests/functional/api/static/announcements.spec.ts
new file mode 100644
index 0000000..ac933fe
--- /dev/null
+++ b/tests/functional/api/static/announcements.spec.ts
@@ -0,0 +1,55 @@
1import { test } from '@japa/runner';
2import { apiVersion } from '../../../config';
3
4test.group('API / Static / News', () => {
5 test('returns a 404 response when the requested versions does not exist', async ({
6 client,
7 }) => {
8 const response = await client.get(`/${apiVersion}/announcements/illegal`);
9
10 response.assertStatus(404);
11 response.assertTextIncludes('No announcement found.');
12 });
13
14 test('returns a 200 response with default version file when specifying version as input', async ({
15 client,
16 }) => {
17 const response = await client.get(`/${apiVersion}/announcements/version`);
18
19 response.assertStatus(200);
20 response.assertBody({
21 main: {
22 headline: 'Example Announcement',
23 subHeadline: 'Configure your announcement here',
24 image: {
25 light: 'https://api.ferdium.org/assets/feature/light.png',
26 dark: 'https://api.ferdium.org/assets/feature/dark.png',
27 },
28 text: 'Long description here',
29 cta: {
30 label: 'Click here to do something',
31 href: '/settings/app',
32 analytics: {
33 category: 'announcements-main',
34 action: 'event',
35 label: 'This does not get used',
36 },
37 },
38 },
39 spotlight: {
40 title: 'Spotlight:',
41 subject: 'Show another feature',
42 text: 'Show another feature in the spotlight',
43 cta: {
44 label: 'Click here to do something',
45 href: '/settings/team',
46 analytics: {
47 category: 'announcements-spotlight',
48 action: 'event',
49 label: 'This does not get used',
50 },
51 },
52 },
53 });
54 });
55});
diff --git a/tests/functional/api/static/features.spec.ts b/tests/functional/api/static/features.spec.ts
new file mode 100644
index 0000000..1050fcf
--- /dev/null
+++ b/tests/functional/api/static/features.spec.ts
@@ -0,0 +1,29 @@
1import { test } from '@japa/runner';
2import { apiVersion } from '../../../config';
3
4const defaultResponse = {
5 isServiceProxyEnabled: true,
6 isWorkspaceEnabled: true,
7 isAnnouncementsEnabled: true,
8 isSettingsWSEnabled: false,
9 isMagicBarEnabled: true,
10 isTodosEnabled: true,
11};
12
13test.group('API / Static / Features', () => {
14 test('returns a 200 response with empty array', async ({ client }) => {
15 const response = await client.get(`/${apiVersion}/features`);
16
17 response.assertStatus(200);
18 response.assertBody(defaultResponse);
19 });
20
21 test('returns a 200 response with expected object when calling with :mode', async ({
22 client,
23 }) => {
24 const response = await client.get(`/${apiVersion}/features/random`);
25
26 response.assertStatus(200);
27 response.assertBody(defaultResponse);
28 });
29});
diff --git a/tests/functional/api/static/news.spec.ts b/tests/functional/api/static/news.spec.ts
new file mode 100644
index 0000000..8f3818f
--- /dev/null
+++ b/tests/functional/api/static/news.spec.ts
@@ -0,0 +1,11 @@
1import { test } from '@japa/runner';
2import { apiVersion } from '../../../config';
3
4test.group('API / Static / News', () => {
5 test('returns a 200 response with empty array', async ({ client }) => {
6 const response = await client.get(`/${apiVersion}/news`);
7
8 response.assertStatus(200);
9 response.assertBody([]);
10 });
11});
diff --git a/tests/functional/api/static/services.spec.ts b/tests/functional/api/static/services.spec.ts
new file mode 100644
index 0000000..55eef37
--- /dev/null
+++ b/tests/functional/api/static/services.spec.ts
@@ -0,0 +1,11 @@
1import { test } from '@japa/runner';
2import { apiVersion } from '../../../config';
3
4test.group('API / Static / Services', () => {
5 test('returns a 200 response with empty array', async ({ client }) => {
6 const response = await client.get(`/${apiVersion}/services`);
7
8 response.assertStatus(200);
9 response.assertBody([]);
10 });
11});
diff --git a/tests/functional/dashboard/account.spec.ts b/tests/functional/dashboard/account.spec.ts
new file mode 100644
index 0000000..bee9d6a
--- /dev/null
+++ b/tests/functional/dashboard/account.spec.ts
@@ -0,0 +1,124 @@
1import { test } from '@japa/runner';
2import User from 'App/Models/User';
3import UserFactory from 'Database/factories/UserFactory';
4
5test.group('Dashboard / Account page', () => {
6 test('redirects to /user/login when accessing /user/account as guest', async ({
7 client,
8 }) => {
9 const response = await client.get('/user/account');
10
11 response.assertRedirectsTo('/user/login'); // Check if it redirects to the expected URL
12 });
13
14 test('returns a 200 opening the account route while logged in', async ({
15 client,
16 }) => {
17 const user = await UserFactory.create();
18 const response = await client.get('/user/account').loginAs(user);
19
20 response.assertStatus(200);
21 response.assertTextIncludes('Your Ferdium account');
22
23 response.assertTextIncludes(user.email);
24 response.assertTextIncludes(user.username);
25 response.assertTextIncludes(user.lastname);
26 });
27
28 test('returns a validation error for all fields when missing', async ({
29 client,
30 }) => {
31 const user = await UserFactory.create();
32 const response = await client.post('/user/account').loginAs(user);
33
34 response.assertTextIncludes(
35 'value="required validation failed,required validation failed" placeholder="E-Mail"',
36 );
37 response.assertTextIncludes(
38 'value="required validation failed,required validation failed" placeholder="Name"',
39 );
40 response.assertTextIncludes(
41 'value="required validation failed,required validation failed" placeholder="Last Name"',
42 );
43 });
44
45 test('returns a validation error for username when there is another user with same username', async ({
46 client,
47 }) => {
48 const user = await UserFactory.create();
49 const existingUser = await UserFactory.create();
50
51 const response = await client.post('/user/account').loginAs(user).form({
52 username: existingUser.username,
53 email: user.email,
54 lastname: user.lastname,
55 });
56
57 response.assertTextIncludes(
58 'value="unique validation failure" placeholder="Name"',
59 );
60 });
61
62 test('returns a validation error for email when there is another user with same email', async ({
63 client,
64 }) => {
65 const user = await UserFactory.create();
66 const existingUser = await UserFactory.create();
67
68 const response = await client.post('/user/account').loginAs(user).form({
69 username: user.username,
70 email: existingUser.email,
71 lastname: user.lastname,
72 });
73
74 response.assertTextIncludes(
75 'value="unique validation failure" placeholder="E-Mail"',
76 );
77 });
78
79 test('updates user data and ensures the data is persisted', async ({
80 client,
81 assert,
82 }) => {
83 const user = await UserFactory.create();
84 const response = await client.post('/user/account').loginAs(user).form({
85 username: 'edited-username',
86 email: 'edited-email',
87 lastname: 'edited-lastname',
88 });
89
90 response.assertStatus(200);
91
92 // Ensure updated data is displayed on account page
93 response.assertTextIncludes('edited-username');
94 response.assertTextIncludes('edited-email');
95 response.assertTextIncludes('edited-lastname');
96
97 // Ensure updated data is persisted in database
98 const updatedUser = await User.findBy('id', user.id);
99 assert.equal(updatedUser?.username, 'edited-username');
100 assert.equal(updatedUser?.email, 'edited-email');
101 assert.equal(updatedUser?.lastname, 'edited-lastname');
102 });
103
104 test('updates user password and ensures the user can still login', async ({
105 client,
106 }) => {
107 const user = await UserFactory.create();
108 const response = await client.post('/user/account').loginAs(user).form({
109 username: user.username,
110 email: user.email,
111 lastname: user.lastname,
112 password: 'modified-password-account-page',
113 });
114
115 response.assertStatus(200);
116
117 const loginResponse = await client.post('/user/login').fields({
118 mail: user.email,
119 password: 'modified-password-account-page',
120 });
121
122 loginResponse.assertRedirectsTo('/user/account');
123 });
124});
diff --git a/tests/functional/dashboard/data.spec.ts b/tests/functional/dashboard/data.spec.ts
new file mode 100644
index 0000000..1a0e7ad
--- /dev/null
+++ b/tests/functional/dashboard/data.spec.ts
@@ -0,0 +1,31 @@
1import { test } from '@japa/runner';
2import UserFactory from 'Database/factories/UserFactory';
3
4test.group('Dashboard / Data page', () => {
5 test('redirects to /user/login when accessing /user/data as guest', async ({
6 client,
7 }) => {
8 const response = await client.get('/user/data');
9
10 response.assertRedirectsTo('/user/login'); // Check if it redirects to the expected URL
11 });
12
13 test('ensure the correct data is shown on the page', async ({ client }) => {
14 const user = await UserFactory.create();
15 const response = await client.get('/user/data').loginAs(user);
16
17 response.assertStatus(200);
18 response.assertTextIncludes(user.email);
19 response.assertTextIncludes(user.username);
20 response.assertTextIncludes(user.lastname);
21 response.assertTextIncludes(
22 user.created_at.toFormat('yyyy-MM-dd HH:mm:ss'),
23 );
24 response.assertTextIncludes(
25 user.updated_at.toFormat('yyyy-MM-dd HH:mm:ss'),
26 );
27 });
28
29 // TODO: Add test to include services.
30 // TODO: Add test to include workspaces.
31});
diff --git a/tests/functional/dashboard/delete.spec.ts b/tests/functional/dashboard/delete.spec.ts
new file mode 100644
index 0000000..ae3f0e6
--- /dev/null
+++ b/tests/functional/dashboard/delete.spec.ts
@@ -0,0 +1,37 @@
1import { test } from '@japa/runner';
2import User from 'App/Models/User';
3import UserFactory from 'Database/factories/UserFactory';
4
5test.group('Dashboard / Delete account page', () => {
6 test('redirects to /user/login when accessing /user/delete as guest', async ({
7 client,
8 }) => {
9 const response = await client.get('/user/delete');
10
11 response.assertRedirectsTo('/user/login'); // Check if it redirects to the expected URL
12 });
13
14 test('returns a 200 opening the delete route while logged in', async ({
15 client,
16 }) => {
17 const user = await UserFactory.create();
18 const response = await client.get('/user/delete').loginAs(user);
19
20 response.assertStatus(200);
21 response.assertTextIncludes('Delete your account');
22 });
23
24 test('returns a 200 opening the delete route while logged in', async ({
25 client,
26 assert,
27 }) => {
28 const user = await UserFactory.create();
29 const response = await client.post('/user/delete').loginAs(user);
30
31 response.assertRedirectsTo('/user/login');
32 // This asserts the session is deleted as well
33 response.assertSessionMissing('auth_web');
34
35 assert.isNull(await User.find(user.id));
36 });
37});
diff --git a/tests/functional/dashboard/disabled-dashboard.spec.ts b/tests/functional/dashboard/disabled-dashboard.spec.ts
new file mode 100644
index 0000000..cc9052c
--- /dev/null
+++ b/tests/functional/dashboard/disabled-dashboard.spec.ts
@@ -0,0 +1,71 @@
1import { test } from '@japa/runner';
2import Config from '@ioc:Adonis/Core/Config';
3
4const disabledDashboardMessage =
5 'The user dashboard is disabled on this server\n\nIf you are the server owner, please set IS_DASHBOARD_ENABLED to true to enable the dashboard.';
6
7test.group('Dashboard / Disabled dashboard', group => {
8 group.setup(() => {
9 Config.set('dashboard.enabled', false);
10 });
11
12 group.teardown(() => {
13 Config.set('dashboard.enabled', true);
14 });
15
16 test('Login page returns disabled dashboard message', async ({ client }) => {
17 const response = await client.get('/user/login');
18
19 response.assertTextIncludes(disabledDashboardMessage);
20 });
21
22 test('Forgot password page returns disabled dashboard message', async ({
23 client,
24 }) => {
25 const response = await client.get('/user/forgot');
26
27 response.assertTextIncludes(disabledDashboardMessage);
28 });
29
30 test('Reset password page returns disabled dashboard message', async ({
31 client,
32 }) => {
33 const response = await client.get('/user/reset');
34
35 response.assertTextIncludes(disabledDashboardMessage);
36 });
37
38 test('Account page returns disabled dashboard message', async ({
39 client,
40 }) => {
41 const response = await client.get('/user/account');
42
43 response.assertTextIncludes(disabledDashboardMessage);
44 });
45
46 test('Data page returns disabled dashboard message', async ({ client }) => {
47 const response = await client.get('/user/data');
48
49 response.assertTextIncludes(disabledDashboardMessage);
50 });
51
52 test('Export page returns disabled dashboard message', async ({ client }) => {
53 const response = await client.get('/user/export');
54
55 response.assertTextIncludes(disabledDashboardMessage);
56 });
57
58 test('Transfer page returns disabled dashboard message', async ({
59 client,
60 }) => {
61 const response = await client.get('/user/transfer');
62
63 response.assertTextIncludes(disabledDashboardMessage);
64 });
65
66 test('Logout page returns disabled dashboard message', async ({ client }) => {
67 const response = await client.get('/user/logout');
68
69 response.assertTextIncludes(disabledDashboardMessage);
70 });
71});
diff --git a/tests/functional/dashboard/export.spec.ts b/tests/functional/dashboard/export.spec.ts
new file mode 100644
index 0000000..4250622
--- /dev/null
+++ b/tests/functional/dashboard/export.spec.ts
@@ -0,0 +1,61 @@
1import { test } from '@japa/runner';
2import UserFactory from 'Database/factories/UserFactory';
3
4test.group('Dashboard / Export page', () => {
5 test('redirects to /user/login when accessing /user/transfer as guest', async ({
6 client,
7 }) => {
8 const response = await client.get('/user/transfer');
9
10 response.assertRedirectsTo('/user/login'); // Check if it redirects to the expected URL
11 });
12
13 test('returns a correct export with user data', async ({
14 assert,
15 client,
16 }) => {
17 const user = await UserFactory.create();
18 const response = await client.get('/user/export').loginAs(user);
19
20 response.assertStatus(200);
21 const exportData = JSON.parse(response.text());
22
23 assert.equal(exportData.username, user.username);
24 assert.equal(exportData.lastname, user.lastname);
25 assert.equal(exportData.mail, user.email);
26 });
27
28 // TODO: We can improve this test by hard checking the export data
29 test('returns a correct export with service data', async ({
30 assert,
31 client,
32 }) => {
33 const user = await UserFactory.with('services', 5).create();
34 const response = await client.get('/user/export').loginAs(user);
35
36 response.assertStatus(200);
37 const exportData = JSON.parse(response.text());
38
39 assert.equal(exportData.username, user.username);
40 assert.equal(exportData.lastname, user.lastname);
41 assert.equal(exportData.mail, user.email);
42 assert.equal(exportData.services.length, user.services.length);
43 });
44
45 // TODO: We can improve this test by hard checking the export data
46 test('returns a correct export with workspace data', async ({
47 assert,
48 client,
49 }) => {
50 const user = await UserFactory.with('workspaces', 5).create();
51 const response = await client.get('/user/export').loginAs(user);
52
53 response.assertStatus(200);
54 const exportData = JSON.parse(response.text());
55
56 assert.equal(exportData.username, user.username);
57 assert.equal(exportData.lastname, user.lastname);
58 assert.equal(exportData.mail, user.email);
59 assert.equal(exportData.workspaces.length, user.workspaces.length);
60 });
61});
diff --git a/tests/functional/dashboard/forgot-password.spec.ts b/tests/functional/dashboard/forgot-password.spec.ts
new file mode 100644
index 0000000..9dcec5a
--- /dev/null
+++ b/tests/functional/dashboard/forgot-password.spec.ts
@@ -0,0 +1,70 @@
1import { test } from '@japa/runner';
2import Event from '@ioc:Adonis/Core/Event';
3import UserFactory from 'Database/factories/UserFactory';
4
5test.group('Dashboard / Forgot password page', () => {
6 test('returns a 200 opening the forgot password route', async ({
7 client,
8 }) => {
9 const response = await client.get('/user/forgot');
10
11 response.assertStatus(200);
12 response.assertTextIncludes('Forgot Password?');
13 });
14
15 test('returns `Please enter a valid email address` when providing invalid email', async ({
16 client,
17 }) => {
18 const response = await client.post('/user/forgot').fields({
19 mail: 'invalid',
20 });
21
22 response.assertStatus(200);
23 response.assertTextIncludes('Please enter a valid email address');
24 });
25
26 test('returns `email send when exists` without forgot:password event', async ({
27 client,
28 assert,
29 }) => {
30 const emitter = Event.fake();
31
32 const response = await client.post('/user/forgot').fields({
33 mail: 'test@ferdium.org',
34 });
35
36 response.assertStatus(200);
37 response.assertTextIncludes(
38 'If your provided E-Mail address is linked to an account, we have just sent an E-Mail to that address.',
39 );
40
41 assert.isFalse(emitter.exists('forgot:password'));
42 });
43
44 test('returns `email send when exists` and trigger forgot:password event', async ({
45 client,
46 assert,
47 }) => {
48 const emitter = Event.fake();
49 const user = await UserFactory.merge({
50 email: 'test+forgot_password@ferdium.org',
51 }).create();
52
53 const response = await client.post('/user/forgot').fields({
54 mail: 'test+forgot_password@ferdium.org',
55 });
56
57 response.assertStatus(200);
58 response.assertTextIncludes(
59 'If your provided E-Mail address is linked to an account, we have just sent an E-Mail to that address.',
60 );
61
62 assert.isTrue(
63 emitter.exists(
64 event =>
65 event.name === 'forgot:password' &&
66 event.data.user.email === user.email,
67 ),
68 );
69 });
70});
diff --git a/tests/functional/dashboard/import-stubs/invalid.json b/tests/functional/dashboard/import-stubs/invalid.json
new file mode 100644
index 0000000..86d11fa
--- /dev/null
+++ b/tests/functional/dashboard/import-stubs/invalid.json
@@ -0,0 +1 @@
Lorum ipsum
diff --git a/tests/functional/dashboard/import-stubs/random-file.txt b/tests/functional/dashboard/import-stubs/random-file.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/functional/dashboard/import-stubs/random-file.txt
diff --git a/tests/functional/dashboard/import-stubs/services-only.ferdi-data b/tests/functional/dashboard/import-stubs/services-only.ferdi-data
new file mode 100644
index 0000000..d95f3ef
--- /dev/null
+++ b/tests/functional/dashboard/import-stubs/services-only.ferdi-data
@@ -0,0 +1,38 @@
1{
2 "username": "John",
3 "lastname": "Doe",
4 "mail": "john.doe@ferdium.org",
5 "services": [
6 {
7 "id": 5641,
8 "userId": "1234",
9 "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b",
10 "name": "random-service-1",
11 "recipeId": "random-service-1",
12 "settings": "{}",
13 "created_at": "2022-06-21 08:29:13",
14 "updated_at": "2022-07-19 15:47:16"
15 },
16 {
17 "id": 2134,
18 "userId": "1234",
19 "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b",
20 "name": "random-service-1",
21 "recipeId": "random-service-1",
22 "settings": "{}",
23 "created_at": "2022-06-21 08:29:13",
24 "updated_at": "2022-07-19 15:47:16"
25 },
26 {
27 "id": 5343,
28 "userId": "1234",
29 "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b",
30 "name": "random-service-1",
31 "recipeId": "random-service-1",
32 "settings": "{}",
33 "created_at": "2022-06-21 08:29:13",
34 "updated_at": "2022-07-19 15:47:16"
35 }
36 ],
37 "workspaces": []
38}
diff --git a/tests/functional/dashboard/import-stubs/services-only.ferdium-data b/tests/functional/dashboard/import-stubs/services-only.ferdium-data
new file mode 100644
index 0000000..d95f3ef
--- /dev/null
+++ b/tests/functional/dashboard/import-stubs/services-only.ferdium-data
@@ -0,0 +1,38 @@
1{
2 "username": "John",
3 "lastname": "Doe",
4 "mail": "john.doe@ferdium.org",
5 "services": [
6 {
7 "id": 5641,
8 "userId": "1234",
9 "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b",
10 "name": "random-service-1",
11 "recipeId": "random-service-1",
12 "settings": "{}",
13 "created_at": "2022-06-21 08:29:13",
14 "updated_at": "2022-07-19 15:47:16"
15 },
16 {
17 "id": 2134,
18 "userId": "1234",
19 "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b",
20 "name": "random-service-1",
21 "recipeId": "random-service-1",
22 "settings": "{}",
23 "created_at": "2022-06-21 08:29:13",
24 "updated_at": "2022-07-19 15:47:16"
25 },
26 {
27 "id": 5343,
28 "userId": "1234",
29 "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b",
30 "name": "random-service-1",
31 "recipeId": "random-service-1",
32 "settings": "{}",
33 "created_at": "2022-06-21 08:29:13",
34 "updated_at": "2022-07-19 15:47:16"
35 }
36 ],
37 "workspaces": []
38}
diff --git a/tests/functional/dashboard/import-stubs/services-only.json b/tests/functional/dashboard/import-stubs/services-only.json
new file mode 100644
index 0000000..d95f3ef
--- /dev/null
+++ b/tests/functional/dashboard/import-stubs/services-only.json
@@ -0,0 +1,38 @@
1{
2 "username": "John",
3 "lastname": "Doe",
4 "mail": "john.doe@ferdium.org",
5 "services": [
6 {
7 "id": 5641,
8 "userId": "1234",
9 "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b",
10 "name": "random-service-1",
11 "recipeId": "random-service-1",
12 "settings": "{}",
13 "created_at": "2022-06-21 08:29:13",
14 "updated_at": "2022-07-19 15:47:16"
15 },
16 {
17 "id": 2134,
18 "userId": "1234",
19 "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b",
20 "name": "random-service-1",
21 "recipeId": "random-service-1",
22 "settings": "{}",
23 "created_at": "2022-06-21 08:29:13",
24 "updated_at": "2022-07-19 15:47:16"
25 },
26 {
27 "id": 5343,
28 "userId": "1234",
29 "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b",
30 "name": "random-service-1",
31 "recipeId": "random-service-1",
32 "settings": "{}",
33 "created_at": "2022-06-21 08:29:13",
34 "updated_at": "2022-07-19 15:47:16"
35 }
36 ],
37 "workspaces": []
38}
diff --git a/tests/functional/dashboard/import-stubs/services-workspaces.ferdi-data b/tests/functional/dashboard/import-stubs/services-workspaces.ferdi-data
new file mode 100644
index 0000000..750e6ca
--- /dev/null
+++ b/tests/functional/dashboard/import-stubs/services-workspaces.ferdi-data
@@ -0,0 +1,61 @@
1{
2 "username": "John",
3 "lastname": "Doe",
4 "mail": "john.doe@ferdium.org",
5 "services": [
6 {
7 "id": 5641,
8 "userId": "1234",
9 "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b",
10 "name": "random-service-1",
11 "recipeId": "random-service-1",
12 "settings": "{}",
13 "created_at": "2022-06-21 08:29:13",
14 "updated_at": "2022-07-19 15:47:16"
15 },
16 {
17 "id": 2134,
18 "userId": "1234",
19 "serviceId": "79769de5-a998-4af1-b7d0-89956a15b0ed",
20 "name": "random-service-2",
21 "recipeId": "random-service-2",
22 "settings": "{}",
23 "created_at": "2022-06-21 08:29:13",
24 "updated_at": "2022-07-19 15:47:16"
25 },
26 {
27 "id": 5343,
28 "userId": "1234",
29 "serviceId": "0ac973f8-40dc-4760-b2c2-55e1d2943747",
30 "name": "random-service-3",
31 "recipeId": "random-service-3",
32 "settings": "{}",
33 "created_at": "2022-06-21 08:29:13",
34 "updated_at": "2022-07-19 15:47:16"
35 }
36 ],
37 "workspaces": [
38 {
39 "name": "workspace1",
40 "order": 0,
41 "services": [],
42 "data": "{\"name\":\"workspace1\"}"
43 },
44 {
45 "name": "workspace2",
46 "order": 0,
47 "services": ["d6901fff-ec44-4251-93de-d7103ed9c44b", "79769de5-a998-4af1-b7d0-89956a15b0ed"],
48 "data": "{\"name\":\"workspace2\"}"
49 },
50 {
51 "name": "workspace3",
52 "order": 0,
53 "services": [
54 "d6901fff-ec44-4251-93de-d7103ed9c44b",
55 "79769de5-a998-4af1-b7d0-89956a15b0ed",
56 "0ac973f8-40dc-4760-b2c2-55e1d2943747"
57 ],
58 "data": "{\"name\":\"workspace3\"}"
59 }
60 ]
61}
diff --git a/tests/functional/dashboard/import-stubs/services-workspaces.ferdium-data b/tests/functional/dashboard/import-stubs/services-workspaces.ferdium-data
new file mode 100644
index 0000000..e999c0d
--- /dev/null
+++ b/tests/functional/dashboard/import-stubs/services-workspaces.ferdium-data
@@ -0,0 +1,61 @@
1{
2 "username": "John",
3 "lastname": "Doe",
4 "mail": "john.doe@ferdium.org",
5 "services": [
6 {
7 "id": 5641,
8 "userId": "1234",
9 "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b",
10 "name": "random-service-1",
11 "recipeId": "random-service-1",
12 "settings": "{}",
13 "created_at": "2022-06-21 08:29:13",
14 "updated_at": "2022-07-19 15:47:16"
15 },
16 {
17 "id": 2134,
18 "userId": "1234",
19 "serviceId": "79769de5-a998-4af1-b7d0-89956a15b0ed",
20 "name": "random-service-2",
21 "recipeId": "random-service-2",
22 "settings": "{}",
23 "created_at": "2022-06-21 08:29:13",
24 "updated_at": "2022-07-19 15:47:16"
25 },
26 {
27 "id": 5343,
28 "userId": "1234",
29 "serviceId": "0ac973f8-40dc-4760-b2c2-55e1d2943747",
30 "name": "random-service-3",
31 "recipeId": "random-service-2",
32 "settings": "{}",
33 "created_at": "2022-06-21 08:29:13",
34 "updated_at": "2022-07-19 15:47:16"
35 }
36 ],
37 "workspaces": [
38 {
39 "name": "workspace1",
40 "order": 0,
41 "services": [],
42 "data": "{\"name\":\"workspace1\"}"
43 },
44 {
45 "name": "workspace2",
46 "order": 0,
47 "services": ["d6901fff-ec44-4251-93de-d7103ed9c44b", "79769de5-a998-4af1-b7d0-89956a15b0ed"],
48 "data": "{\"name\":\"workspace2\"}"
49 },
50 {
51 "name": "workspace3",
52 "order": 0,
53 "services": [
54 "d6901fff-ec44-4251-93de-d7103ed9c44b",
55 "79769de5-a998-4af1-b7d0-89956a15b0ed",
56 "0ac973f8-40dc-4760-b2c2-55e1d2943747"
57 ],
58 "data": "{\"name\":\"workspace3\"}"
59 }
60 ]
61}
diff --git a/tests/functional/dashboard/import-stubs/services-workspaces.json b/tests/functional/dashboard/import-stubs/services-workspaces.json
new file mode 100644
index 0000000..54c6889
--- /dev/null
+++ b/tests/functional/dashboard/import-stubs/services-workspaces.json
@@ -0,0 +1,64 @@
1{
2 "username": "John",
3 "lastname": "Doe",
4 "mail": "john.doe@ferdium.org",
5 "services": [
6 {
7 "id": 5641,
8 "userId": "1234",
9 "serviceId": "d6901fff-ec44-4251-93de-d7103ed9c44b",
10 "name": "random-service-1",
11 "recipeId": "random-service-1",
12 "settings": "{}",
13 "created_at": "2022-06-21 08:29:13",
14 "updated_at": "2022-07-19 15:47:16"
15 },
16 {
17 "id": 2134,
18 "userId": "1234",
19 "serviceId": "79769de5-a998-4af1-b7d0-89956a15b0ed",
20 "name": "random-service-2",
21 "recipeId": "random-service-2",
22 "settings": "{}",
23 "created_at": "2022-06-21 08:29:13",
24 "updated_at": "2022-07-19 15:47:16"
25 },
26 {
27 "id": 5343,
28 "userId": "1234",
29 "serviceId": "0ac973f8-40dc-4760-b2c2-55e1d2943747",
30 "name": "random-service-3",
31 "recipeId": "random-service-3",
32 "settings": "{}",
33 "created_at": "2022-06-21 08:29:13",
34 "updated_at": "2022-07-19 15:47:16"
35 }
36 ],
37 "workspaces": [
38 {
39 "name": "workspace1",
40 "order": 0,
41 "services": [],
42 "data": "{\"name\":\"workspace1\"}"
43 },
44 {
45 "name": "workspace2",
46 "order": 0,
47 "services": [
48 "d6901fff-ec44-4251-93de-d7103ed9c44b",
49 "79769de5-a998-4af1-b7d0-89956a15b0ed"
50 ],
51 "data": "{\"name\":\"workspace2\"}"
52 },
53 {
54 "name": "workspace3",
55 "order": 0,
56 "services": [
57 "d6901fff-ec44-4251-93de-d7103ed9c44b",
58 "79769de5-a998-4af1-b7d0-89956a15b0ed",
59 "0ac973f8-40dc-4760-b2c2-55e1d2943747"
60 ],
61 "data": "{\"name\":\"workspace3\"}"
62 }
63 ]
64}
diff --git a/tests/functional/dashboard/import-stubs/valid-no-data.json b/tests/functional/dashboard/import-stubs/valid-no-data.json
new file mode 100644
index 0000000..8d4816f
--- /dev/null
+++ b/tests/functional/dashboard/import-stubs/valid-no-data.json
@@ -0,0 +1,3 @@
1{
2 "test": "value"
3}
diff --git a/tests/functional/dashboard/import-stubs/workspaces-only.ferdi-data b/tests/functional/dashboard/import-stubs/workspaces-only.ferdi-data
new file mode 100644
index 0000000..13ea9c6
--- /dev/null
+++ b/tests/functional/dashboard/import-stubs/workspaces-only.ferdi-data
@@ -0,0 +1,26 @@
1{
2 "username": "John",
3 "lastname": "Doe",
4 "mail": "john.doe@ferdium.org",
5 "services": [],
6 "workspaces": [
7 {
8 "name": "workspace1",
9 "order": 0,
10 "services": [],
11 "data": "{\"name\":\"workspace1\"}"
12 },
13 {
14 "name": "workspace2",
15 "order": 0,
16 "services": [],
17 "data": "{\"name\":\"workspace2\"}"
18 },
19 {
20 "name": "workspace3",
21 "order": 0,
22 "services": [],
23 "data": "{\"name\":\"workspace3\"}"
24 }
25 ]
26}
diff --git a/tests/functional/dashboard/import-stubs/workspaces-only.ferdium-data b/tests/functional/dashboard/import-stubs/workspaces-only.ferdium-data
new file mode 100644
index 0000000..13ea9c6
--- /dev/null
+++ b/tests/functional/dashboard/import-stubs/workspaces-only.ferdium-data
@@ -0,0 +1,26 @@
1{
2 "username": "John",
3 "lastname": "Doe",
4 "mail": "john.doe@ferdium.org",
5 "services": [],
6 "workspaces": [
7 {
8 "name": "workspace1",
9 "order": 0,
10 "services": [],
11 "data": "{\"name\":\"workspace1\"}"
12 },
13 {
14 "name": "workspace2",
15 "order": 0,
16 "services": [],
17 "data": "{\"name\":\"workspace2\"}"
18 },
19 {
20 "name": "workspace3",
21 "order": 0,
22 "services": [],
23 "data": "{\"name\":\"workspace3\"}"
24 }
25 ]
26}
diff --git a/tests/functional/dashboard/import-stubs/workspaces-only.json b/tests/functional/dashboard/import-stubs/workspaces-only.json
new file mode 100644
index 0000000..13ea9c6
--- /dev/null
+++ b/tests/functional/dashboard/import-stubs/workspaces-only.json
@@ -0,0 +1,26 @@
1{
2 "username": "John",
3 "lastname": "Doe",
4 "mail": "john.doe@ferdium.org",
5 "services": [],
6 "workspaces": [
7 {
8 "name": "workspace1",
9 "order": 0,
10 "services": [],
11 "data": "{\"name\":\"workspace1\"}"
12 },
13 {
14 "name": "workspace2",
15 "order": 0,
16 "services": [],
17 "data": "{\"name\":\"workspace2\"}"
18 },
19 {
20 "name": "workspace3",
21 "order": 0,
22 "services": [],
23 "data": "{\"name\":\"workspace3\"}"
24 }
25 ]
26}
diff --git a/tests/functional/dashboard/login.spec.ts b/tests/functional/dashboard/login.spec.ts
new file mode 100644
index 0000000..adae66a
--- /dev/null
+++ b/tests/functional/dashboard/login.spec.ts
@@ -0,0 +1,65 @@
1import { test } from '@japa/runner';
2import UserFactory from 'Database/factories/UserFactory';
3
4test.group('Dashboard / Login page', () => {
5 test('returns a 200 opening the login route', async ({ client }) => {
6 const response = await client.get('/user/login');
7
8 response.assertStatus(200);
9 });
10
11 test('returns `invalid mail or password` when validation fails', async ({
12 client,
13 }) => {
14 const response = await client.post('/user/login').fields({
15 mail: 'invalid',
16 password: 'invalid',
17 });
18
19 response.assertRedirectsTo('/user/login');
20 response.assertTextIncludes('Invalid mail or password');
21 });
22
23 test('returns `invalid mail or password` when account is not found', async ({
24 client,
25 }) => {
26 const response = await client.post('/user/login').fields({
27 mail: 'test+notexistingpassword@ferdium.org',
28 password: 'notexistingpassword',
29 });
30
31 response.assertRedirectsTo('/user/login');
32 response.assertTextIncludes('Invalid mail or password');
33 });
34
35 test('returns `invalid mail or password` when password is not valid', async ({
36 client,
37 }) => {
38 await UserFactory.merge({
39 email: 'test@ferdium.org',
40 }).create();
41
42 const response = await client.post('/user/login').fields({
43 mail: 'test+invalid_password@ferdium.org',
44 password: 'invalid_password',
45 });
46
47 response.assertRedirectsTo('/user/login');
48 response.assertTextIncludes('Invalid mail or password');
49 });
50
51 test('redirects to account page when user is able to login', async ({
52 client,
53 }) => {
54 await UserFactory.merge({
55 email: 'test+password@ferdium.org',
56 }).create();
57
58 const response = await client.post('/user/login').fields({
59 mail: 'test+password@ferdium.org',
60 password: 'password',
61 });
62
63 response.assertRedirectsTo('/user/account');
64 });
65});
diff --git a/tests/functional/dashboard/logout.spec.ts b/tests/functional/dashboard/logout.spec.ts
new file mode 100644
index 0000000..a45ee72
--- /dev/null
+++ b/tests/functional/dashboard/logout.spec.ts
@@ -0,0 +1,21 @@
1import { test } from '@japa/runner';
2import UserFactory from 'Database/factories/UserFactory';
3
4test.group('Dashboard / Log out page', () => {
5 test('redirects to /user/login when accessing /user/logout as guest', async ({
6 client,
7 }) => {
8 const response = await client.get('/user/logout');
9
10 response.assertRedirectsTo('/user/login'); // Check if it redirects to the expected URL
11 });
12
13 test('logs the user out when opening the page', async ({ client }) => {
14 const user = await UserFactory.create();
15 const response = await client.get('/user/logout').loginAs(user);
16
17 response.assertRedirectsTo('/user/login');
18 // This asserts the session is deleted as well
19 response.assertSessionMissing('auth_web');
20 });
21});
diff --git a/tests/functional/dashboard/reset-password.spec.ts b/tests/functional/dashboard/reset-password.spec.ts
new file mode 100644
index 0000000..e488482
--- /dev/null
+++ b/tests/functional/dashboard/reset-password.spec.ts
@@ -0,0 +1,122 @@
1import { test } from '@japa/runner';
2import Token from 'App/Models/Token';
3import UserFactory from 'Database/factories/UserFactory';
4import TokenFactory from 'Database/factories/TokenFactory';
5
6test.group('Dashboard / Reset password page', () => {
7 test('returns a `Invalid token` message when opening without a token', async ({
8 client,
9 }) => {
10 const response = await client.get('/user/reset');
11
12 response.assertStatus(200);
13 response.assertTextIncludes('Invalid token');
14 });
15
16 test('displays the form when a token is provided', async ({ client }) => {
17 const response = await client.get(
18 '/user/reset?token=randomtokenbutitworks',
19 );
20
21 response.assertStatus(200);
22 response.assertTextIncludes('Reset Your Password');
23 });
24
25 test('returns `passwords do not match` message when passwords do not match', async ({
26 client,
27 }) => {
28 const response = await client.post('/user/reset').fields({
29 token: 'randomnotworkingtoken',
30 password: 'password',
31 password_confirmation: 'not_matching',
32 });
33
34 response.assertTextIncludes('Passwords do not match');
35 });
36
37 test('returns `Cannot reset your password` when token does not exist', async ({
38 client,
39 }) => {
40 const response = await client.post('/user/reset').fields({
41 token: 'randomnotworkingtoken',
42 password: 'password',
43 password_confirmation: 'password',
44 });
45
46 response.assertTextIncludes('Cannot reset your password');
47 });
48
49 test('returns `Cannot reset your password` when token is older than 24 hours', async ({
50 client,
51 }) => {
52 const token = await TokenFactory.merge({
53 // eslint-disable-next-line unicorn/no-await-expression-member
54 user_id: (await UserFactory.create()).id,
55 })
56 .apply('old_token')
57 .create();
58
59 const response = await client.post('/user/reset').fields({
60 token: token.token,
61 password: 'password',
62 password_confirmation: 'password',
63 });
64
65 response.assertTextIncludes('Cannot reset your password');
66 });
67
68 test('returns `Cannot reset your password` when token is revoked', async ({
69 client,
70 }) => {
71 const token = await TokenFactory.merge({
72 // eslint-disable-next-line unicorn/no-await-expression-member
73 user_id: (await UserFactory.create()).id,
74 })
75 .apply('revoked')
76 .create();
77
78 const response = await client.post('/user/reset').fields({
79 token: token.token,
80 password: 'password',
81 password_confirmation: 'password',
82 });
83
84 response.assertTextIncludes('Cannot reset your password');
85 });
86
87 test('correctly resets password and deletes token and able to login with new password', async ({
88 client,
89 assert,
90 }) => {
91 const userEmail = 'working-reset-password-login@ferdium.org';
92 const token = await TokenFactory.merge({
93 user_id:
94 (
95 await UserFactory.merge({
96 email: userEmail,
97 }).create()
98 // prettier-ignore
99 // eslint-disable-next-line unicorn/no-await-expression-member
100 ).id,
101 }).create();
102
103 const response = await client.post('/user/reset').fields({
104 token: token.token,
105 password: 'new_password',
106 password_confirmation: 'new_password',
107 });
108
109 // Assert response is as expected
110 response.assertTextIncludes('Successfully reset your password');
111
112 // Token should be deleted from database
113 assert.isNull(await Token.query().where('token', token.token).first());
114
115 const loginResponse = await client.post('/user/login').fields({
116 mail: userEmail,
117 password: 'new_password',
118 });
119
120 loginResponse.assertRedirectsTo('/user/account');
121 });
122});
diff --git a/tests/functional/dashboard/transfer.spec.ts b/tests/functional/dashboard/transfer.spec.ts
new file mode 100644
index 0000000..0f8ee31
--- /dev/null
+++ b/tests/functional/dashboard/transfer.spec.ts
@@ -0,0 +1,222 @@
1import { test } from '@japa/runner';
2import UserFactory from 'Database/factories/UserFactory';
3
4test.group('Dashboard / Transfer page', () => {
5 test('redirects to /user/login when accessing /user/transfer as guest', async ({
6 client,
7 }) => {
8 const response = await client.get('/user/transfer');
9
10 response.assertRedirectsTo('/user/login'); // Check if it redirects to the expected URL
11 });
12
13 test('returns a 200 opening the transfer route while being logged in', async ({
14 client,
15 }) => {
16 const user = await UserFactory.create();
17 const response = await client.get('/user/transfer').loginAs(user);
18
19 response.assertStatus(200);
20 });
21
22 // TODO: Fix the following tests
23
24 // test('returns a validation error when not uploading a file', async ({
25 // client,
26 // }) => {
27 // const user = await UserFactory.create();
28 // const response = await client.put('/user/transfer').loginAs(user);
29
30 // response.assertTextIncludes('File missing');
31 // });
32
33 // test('returns a validation error when trying to use anything but json', async ({
34 // client,
35 // }) => {
36 // const user = await UserFactory.create();
37 // const response = await client
38 // .put('/user/transfer')
39 // .loginAs(user)
40 // .file('file', 'tests/functional/dashboard/import-stubs/random-file.txt', {
41 // filename: 'random-file.txt',
42 // });
43
44 // response.assertTextIncludes('File missing');
45 // });
46
47 // test('returns a validation error when trying to use anything valid json', async ({
48 // client,
49 // }) => {
50 // const user = await UserFactory.create();
51 // const response = await client
52 // .put('/user/transfer')
53 // .loginAs(user)
54 // .file('file', 'tests/functional/dashboard/import-stubs/invalid.json', {
55 // filename: 'invalid.json',
56 // });
57
58 // response.assertTextIncludes('Invalid Ferdium account file');
59 // });
60
61 // test('returns a validation error when trying to use a json file with no ferdium structure', async ({
62 // client,
63 // }) => {
64 // const user = await UserFactory.create();
65 // const response = await client
66 // .put('/user/transfer')
67 // .loginAs(user)
68 // .file(
69 // 'file',
70 // 'tests/functional/dashboard/import-stubs/valid-no-data.json',
71 // {
72 // filename: 'valid-no-data.json',
73 // },
74 // );
75
76 // response.assertTextIncludes('Invalid Ferdium account file (2)');
77 // });
78
79 // test('correctly transfers data from json/ferdi-data and ferdium-data file with only workspaces', async ({
80 // client,
81 // assert,
82 // }) => {
83 // // Repeat for all 3 file extension
84 // const files = [
85 // 'workspaces-only.json',
86 // 'workspaces-only.ferdi-data',
87 // 'workspaces-only.ferdium-data',
88 // ];
89
90 // for (const file of files) {
91 // // eslint-disable-next-line no-await-in-loop
92 // const user = await UserFactory.create();
93 // // eslint-disable-next-line no-await-in-loop
94 // const response = await client
95 // .put('/user/transfer')
96 // .loginAs(user)
97 // .file('file', `tests/functional/dashboard/import-stubs/${file}`, {
98 // filename: file,
99 // });
100
101 // response.assertTextIncludes(
102 // 'Your account has been imported, you can now login as usual!',
103 // );
104 // // eslint-disable-next-line no-await-in-loop
105 // await user.refresh();
106
107 // // eslint-disable-next-line no-await-in-loop
108 // const workspacesForUser = await user.related('workspaces').query();
109 // assert.equal(workspacesForUser.length, 3);
110 // }
111 // });
112
113 // test('correctly transfers data from json/ferdi-data and ferdium-data file with only services', async ({
114 // client,
115 // assert,
116 // }) => {
117 // // Repeat for all 3 file extension
118 // const files = [
119 // 'services-only.json',
120 // 'services-only.ferdi-data',
121 // 'services-only.ferdium-data',
122 // ];
123
124 // for (const file of files) {
125 // // eslint-disable-next-line no-await-in-loop
126 // const user = await UserFactory.create();
127 // // eslint-disable-next-line no-await-in-loop
128 // const response = await client
129 // .put('/user/transfer')
130 // .loginAs(user)
131 // .file('file', `tests/functional/dashboard/import-stubs/${file}`, {
132 // filename: file,
133 // });
134
135 // response.assertTextIncludes(
136 // 'Your account has been imported, you can now login as usual!',
137 // );
138 // // eslint-disable-next-line no-await-in-loop
139 // await user.refresh();
140
141 // // eslint-disable-next-line no-await-in-loop
142 // const servicesForUser = await user.related('services').query();
143 // assert.equal(servicesForUser.length, 3);
144 // }
145 // });
146
147 // test('correctly transfers data from json/ferdi-data and ferdium-data file with workspaces and services', async ({
148 // client,
149 // assert,
150 // }) => {
151 // // Repeat for all 3 file extension
152 // const files = [
153 // 'services-workspaces.json',
154 // 'services-workspaces.ferdi-data',
155 // 'services-workspaces.ferdium-data',
156 // ];
157
158 // for (const file of files) {
159 // // eslint-disable-next-line no-await-in-loop
160 // const user = await UserFactory.create();
161 // // eslint-disable-next-line no-await-in-loop
162 // const response = await client
163 // .put('/user/transfer')
164 // .loginAs(user)
165 // .file('file', `tests/functional/dashboard/import-stubs/${file}`, {
166 // filename: file,
167 // });
168
169 // response.assertTextIncludes(
170 // 'Your account has been imported, you can now login as usual!',
171 // );
172 // // eslint-disable-next-line no-await-in-loop
173 // await user.refresh();
174
175 // // eslint-disable-next-line no-await-in-loop
176 // const servicesForUser = await user.related('services').query();
177 // // eslint-disable-next-line no-await-in-loop
178 // const workspacesForUser = await user.related('workspaces').query();
179 // assert.equal(servicesForUser.length, 3);
180 // assert.equal(workspacesForUser.length, 3);
181
182 // const firstServiceUuid = servicesForUser.find(
183 // s => s.name === 'random-service-1',
184 // )?.serviceId;
185 // const secondServiceUuid = servicesForUser.find(
186 // s => s.name === 'random-service-2',
187 // )?.serviceId;
188 // const thirdServiceUUid = servicesForUser.find(
189 // s => s.name === 'random-service-3',
190 // )?.serviceId;
191
192 // // Assert the first workspace to not have any services
193 // const firstWorkspace = workspacesForUser.find(
194 // w => w.name === 'workspace1',
195 // );
196 // if (firstWorkspace?.services) {
197 // assert.empty(JSON.parse(firstWorkspace.services));
198 // }
199
200 // const secondWorkspace = workspacesForUser.find(
201 // w => w.name === 'workspace2',
202 // );
203 // if (secondWorkspace?.services) {
204 // assert.deepEqual(JSON.parse(secondWorkspace.services), [
205 // firstServiceUuid,
206 // secondServiceUuid,
207 // ]);
208 // }
209
210 // const thirdWorkspace = workspacesForUser.find(
211 // w => w.name === 'workspace3',
212 // );
213 // if (thirdWorkspace?.services) {
214 // assert.deepEqual(JSON.parse(thirdWorkspace.services), [
215 // firstServiceUuid,
216 // secondServiceUuid,
217 // thirdServiceUUid,
218 // ]);
219 // }
220 // }
221 // });
222});
diff --git a/tests/functional/health.spec.ts b/tests/functional/health.spec.ts
new file mode 100644
index 0000000..2f7e074
--- /dev/null
+++ b/tests/functional/health.spec.ts
@@ -0,0 +1,13 @@
1import { test } from '@japa/runner';
2
3test.group('health page', () => {
4 test('returns a 200 response', async ({ client }) => {
5 const response = await client.get('/health');
6
7 response.assertStatus(200);
8 response.assertBodyContains({
9 api: 'success',
10 db: 'success',
11 });
12 });
13});
diff --git a/tests/functional/static-pages/home.spec.ts b/tests/functional/static-pages/home.spec.ts
new file mode 100644
index 0000000..5054e05
--- /dev/null
+++ b/tests/functional/static-pages/home.spec.ts
@@ -0,0 +1,10 @@
1import { test } from '@japa/runner';
2
3test.group('home page', () => {
4 test('returns a 200 response', async ({ client }) => {
5 const response = await client.get('/');
6
7 response.assertStatus(200);
8 response.assertTextIncludes('Go to account dashboard');
9 });
10});
diff --git a/tests/functional/static-pages/privacy.spec.ts b/tests/functional/static-pages/privacy.spec.ts
new file mode 100644
index 0000000..f1d5bec
--- /dev/null
+++ b/tests/functional/static-pages/privacy.spec.ts
@@ -0,0 +1,10 @@
1import { test } from '@japa/runner';
2
3test.group('privacy page', () => {
4 test('returns a 200 response', async ({ client }) => {
5 const response = await client.get('/privacy');
6
7 response.assertStatus(200);
8 response.assertTextIncludes('PRIVACY POLICY');
9 });
10});
diff --git a/tests/functional/static-pages/terms.spec.ts b/tests/functional/static-pages/terms.spec.ts
new file mode 100644
index 0000000..de990e4
--- /dev/null
+++ b/tests/functional/static-pages/terms.spec.ts
@@ -0,0 +1,10 @@
1import { test } from '@japa/runner';
2
3test.group('terms page', () => {
4 test('returns a 200 response', async ({ client }) => {
5 const response = await client.get('/terms');
6
7 response.assertStatus(200);
8 response.assertTextIncludes('Terms of Service');
9 });
10});
diff --git a/tests/utils.ts b/tests/utils.ts
new file mode 100644
index 0000000..14460b9
--- /dev/null
+++ b/tests/utils.ts
@@ -0,0 +1,6 @@
1import View from '@ioc:Adonis/Core/View';
2
3export function fakeCsrfField(): void {
4 // Create fake csrField function in the view
5 View.global('csrfField', () => '');
6}