Skip to content

Commit 4c6c522

Browse files
authored
test: add probe details e2e coverage (#194)
1 parent 660197c commit 4c6c522

3 files changed

Lines changed: 205 additions & 21 deletions

File tree

‎src/extensions/hooks/adopted-probe/src/update-with-root.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import type { HookExtensionContext } from '@directus/extensions';
22
import _ from 'lodash';
33
import { getContinentByCountry, getContinentName, getCountryByIso, getRegionByCountry, getStateNameByIso } from '../../../lib/src/location/location.js';
44
import { getResetUserFields } from '../../../lib/src/reset-fields.js';
5-
import { type City } from './update-with-user.js';
5+
import { type CityLocation } from './update-with-user.js';
66
import { type Fields, type Probe, payloadError } from './index.js';
77

8-
export const patchCustomLocationRootFields = (fields: Fields, keys: string[], city: City, originalProbe: Probe) => {
8+
export const patchCustomLocationRootFields = (fields: Fields, keys: string[], city: CityLocation, originalProbe: Probe) => {
99
if (keys.length > 1) {
1010
throw payloadError('Batch probe location update is not supported.');
1111
}

‎src/extensions/hooks/adopted-probe/src/update-with-user.ts‎

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export type City = {
2828
adminName1: string;
2929
};
3030

31+
export type CityLocation = Pick<City, 'toponymName' | 'countryCode' | 'adminCode1' | 'lat' | 'lng'>;
32+
3133
const settingsSchema = Joi.object({
3234
meteredConnection: Joi.boolean().strict().required(),
3335
}).unknown(false).required();
@@ -123,27 +125,40 @@ export const patchCustomLocationAllowedFields = async (fields: Fields, keys: str
123125
throw payloadError('State changing is only allowed for US probes.');
124126
}
125127

126-
const response = await axios<{ totalResultsCount?: number; geonames?: City[] }>('http://api.geonames.org/searchJSON', {
127-
params: {
128-
featureClass: 'P',
129-
style: 'medium',
130-
isNameRequired: 'true',
131-
maxRows: '1',
132-
username: env.GEONAMES_USERNAME,
133-
country,
134-
name: fields.city || probe.city,
135-
...fields.state ? { adminCode1: fields.state } : {},
136-
},
137-
timeout: 5000,
138-
});
139-
140-
const cities = response.data.geonames;
141-
142-
if (!cities || cities.length === 0) {
143-
throw payloadError('No matching cities found. Please check the "city" and "country" values, and try using the English version of the name.');
128+
let city: CityLocation;
129+
130+
if (env.ENABLE_E2E_MOCKS === true) {
131+
city = {
132+
toponymName: fields.city || probe.city,
133+
countryCode: country,
134+
adminCode1: fields.state || probe.state || '',
135+
lat: String(probe.latitude),
136+
lng: String(probe.longitude),
137+
};
138+
} else {
139+
const response = await axios<{ totalResultsCount?: number; geonames?: City[] }>('http://api.geonames.org/searchJSON', {
140+
params: {
141+
featureClass: 'P',
142+
style: 'medium',
143+
isNameRequired: 'true',
144+
maxRows: '1',
145+
username: env.GEONAMES_USERNAME,
146+
country,
147+
name: fields.city || probe.city,
148+
...fields.state ? { adminCode1: fields.state } : {},
149+
},
150+
timeout: 5000,
151+
});
152+
153+
const cities = response.data.geonames;
154+
155+
if (!cities || cities.length === 0) {
156+
throw payloadError('No matching cities found. Please check the "city" and "country" values, and try using the English version of the name.');
157+
}
158+
159+
city = cities[0]!;
144160
}
145161

146-
const city = cities[0]!;
147162
city.toponymName = normalizeCityName(city.toponymName);
148163

149164
fields.city = city.toponymName;
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import { randomUUID } from 'node:crypto';
2+
import { test, expect } from '../fixtures.ts';
3+
import { client as sql } from '../client.ts';
4+
import { User } from '../types.ts';
5+
import { randomIP } from '../utils.ts';
6+
7+
const probeDefaults = {
8+
asn: 16019,
9+
city: 'Prague',
10+
country: 'CZ',
11+
countryName: 'Czech Republic',
12+
continent: 'EU',
13+
continentName: 'Europe',
14+
region: 'Eastern Europe',
15+
latitude: 50.07,
16+
longitude: 14.42,
17+
state: null,
18+
name: 'e2e-details-probe',
19+
network: 'Vodafone Czech Republic a.s.',
20+
altIps: JSON.stringify([]),
21+
systemTags: JSON.stringify([ 'eyeball-network' ]),
22+
tags: JSON.stringify([]),
23+
status: 'ready',
24+
version: '0.28.0',
25+
nodeVersion: 'v22.22.3',
26+
allowedCountries: JSON.stringify([ 'CZ' ]),
27+
customLocation: null as string | null,
28+
originalLocation: null as string | null,
29+
settings: JSON.stringify({ meteredConnection: false }),
30+
};
31+
32+
const addProbe = async (user: User, overrides: Partial<typeof probeDefaults> = {}) => {
33+
const probe = {
34+
...probeDefaults,
35+
...overrides,
36+
id: randomUUID(),
37+
uuid: randomUUID(),
38+
ip: randomIP(),
39+
userId: user.id,
40+
date_created: new Date(),
41+
lastSyncDate: new Date(),
42+
};
43+
44+
await sql('gp_probes').insert(probe);
45+
return probe;
46+
};
47+
48+
test('View probe details and return to the probe list', async ({ page, user }) => {
49+
const probe = await addProbe(user);
50+
51+
await page.goto('/probes');
52+
await page.getByRole('link').filter({ hasText: probe.name }).first().click();
53+
await expect(page.getByRole('button', { name: 'Edit probe name' })).toContainText(probe.name);
54+
await expect(page.getByText(probe.ip, { exact: true })).toBeVisible();
55+
await page.getByRole('link', { name: 'Go back to the list of probes' }).click();
56+
await expect(page.getByRole('heading', { name: 'Probes', exact: true })).toBeVisible();
57+
await expect(page.getByRole('link').filter({ hasText: probe.name }).first()).toBeVisible();
58+
});
59+
60+
test('Rename a probe', async ({ page, user }) => {
61+
const probe = await addProbe(user);
62+
await page.goto(`/probes/${probe.id}`);
63+
await page.getByRole('button', { name: 'Edit probe name' }).click();
64+
await page.getByLabel('Probe name input').fill('Renamed probe');
65+
await page.getByRole('button', { name: 'Save probe name' }).click();
66+
await expect(page.getByRole('button', { name: 'Edit probe name' })).toContainText('Renamed probe');
67+
await page.reload();
68+
await expect(page.getByRole('button', { name: 'Edit probe name' })).toContainText('Renamed probe');
69+
});
70+
71+
test('Manage probe tags', async ({ page, user }) => {
72+
const probe = await addProbe(user);
73+
await page.goto(`/probes/${probe.id}`);
74+
await page.getByRole('button', { name: 'Open add tags dialog' }).click();
75+
await page.getByLabel('Your tag', { exact: true }).fill('home');
76+
await page.getByRole('button', { name: 'Save edited tags' }).click();
77+
await expect(page.getByText(`u-${user.github_username}:home`, { exact: true })).toBeVisible();
78+
await page.reload();
79+
await expect(page.getByText(`u-${user.github_username}:home`, { exact: true })).toBeVisible();
80+
81+
await page.getByRole('button', { name: 'Open edit tags dialog' }).click();
82+
await page.getByRole('button', { name: 'Remove tag', exact: true }).click();
83+
await page.getByRole('button', { name: 'Save edited tags' }).click();
84+
await expect(page.getByRole('button', { name: 'Open add tags dialog' })).toBeVisible();
85+
await page.reload();
86+
await expect(page.getByRole('button', { name: 'Open add tags dialog' })).toBeVisible();
87+
await expect(page.getByText(`u-${user.github_username}:home`, { exact: true })).not.toBeVisible();
88+
});
89+
90+
test('Set a custom probe location', async ({ page, user }) => {
91+
const probe = await addProbe(user);
92+
await page.goto(`/probes/${probe.id}`);
93+
await expect(page.getByRole('combobox', { name: 'City name' })).toHaveValue('Prague');
94+
await page.getByRole('combobox', { name: 'City name' }).fill('Brno');
95+
await page.getByRole('button', { name: 'Save', exact: true }).click();
96+
await expect(page.getByText('The probe has been successfully updated', { exact: true })).toBeVisible();
97+
await page.reload();
98+
await expect(page.getByRole('combobox', { name: 'City name' })).toHaveValue('Brno');
99+
});
100+
101+
test('Reset a custom probe location', async ({ page, user }) => {
102+
const probe = await addProbe(user, {
103+
city: 'Brno',
104+
latitude: 49.2,
105+
longitude: 16.61,
106+
customLocation: JSON.stringify({ city: 'Brno', country: 'CZ', state: null, latitude: 49.2, longitude: 16.61 }),
107+
originalLocation: JSON.stringify({ city: 'Prague', country: 'CZ', state: null, latitude: 50.07, longitude: 14.42 }),
108+
});
109+
110+
await page.goto(`/probes/${probe.id}`);
111+
await expect(page.getByRole('combobox', { name: 'City name' })).toHaveValue('Brno');
112+
await page.getByRole('combobox', { name: 'City name' }).fill('');
113+
await page.getByRole('button', { name: 'Save', exact: true }).click();
114+
await expect(page.getByRole('combobox', { name: 'City name' })).toHaveValue('Prague');
115+
await page.reload();
116+
await expect(page.getByRole('combobox', { name: 'City name' })).toHaveValue('Prague');
117+
});
118+
119+
test('Save probe settings', async ({ page, user }) => {
120+
const probe = await addProbe(user);
121+
await page.goto(`/probes/${probe.id}`);
122+
await page.getByRole('tab', { name: 'Settings', exact: true }).click();
123+
await page.getByLabel('Metered connection').check();
124+
await page.getByRole('button', { name: 'Save settings' }).click();
125+
await expect(page.getByText('Probe settings have been successfully updated', { exact: true })).toBeVisible();
126+
await page.reload();
127+
await expect(page.getByLabel('Metered connection')).toBeChecked();
128+
});
129+
130+
test('Restart a probe and view its logs', async ({ page, user }) => {
131+
const probe = await addProbe(user);
132+
await page.route('**/v1/probes/log-scopes', route => route.fulfill({ json: [] }));
133+
await page.route(`**/v1/probes/${probe.id}/restart`, route => route.fulfill({ status: 204 }));
134+
135+
await page.route(`**/v1/probes/${probe.id}/logs**`, route => route.fulfill({
136+
json: {
137+
logs: [{ timestamp: '2024-07-03T09:46:40.000Z', scope: 'system', level: 'info', message: 'Probe started' }],
138+
firstId: '1',
139+
lastId: '1',
140+
hasOlder: false,
141+
},
142+
}));
143+
144+
await page.goto(`/probes/${probe.id}`);
145+
await page.getByRole('button', { name: 'Restart probe' }).click();
146+
await expect(page.getByRole('region', { name: 'Probe logs' })).toContainText('Probe started');
147+
});
148+
149+
test('Delete a probe', async ({ page, user }) => {
150+
const probe = await addProbe(user);
151+
152+
try {
153+
await page.goto(`/probes/${probe.id}`);
154+
await page.getByRole('button', { name: 'Delete probe', exact: true }).click();
155+
await page.getByRole('dialog', { name: 'Delete probe', exact: true }).getByRole('button', { name: 'Delete probe', exact: true }).click();
156+
await expect(page.getByRole('heading', { name: 'Probes', exact: true })).toBeVisible();
157+
await expect(page.getByText('You don\'t have any probes yet.', { exact: true })).toBeVisible();
158+
} finally {
159+
// Fixture cleanup only finds adopted probes; this one now has no owner.
160+
await sql('gp_probes').where({ id: probe.id }).delete();
161+
}
162+
});
163+
164+
test('A probe belonging to another user redirects to the probe list', async ({ page, user2 }) => {
165+
const probe = await addProbe(user2);
166+
await page.goto(`/probes/${probe.id}`);
167+
await expect(page).toHaveURL('/probes');
168+
await expect(page.getByRole('heading', { name: 'Probes', exact: true })).toBeVisible();
169+
});

0 commit comments

Comments
 (0)