Skip to content

Commit bcaeb40

Browse files
committed
expand functional test routes
1 parent e9f37d3 commit bcaeb40

7 files changed

Lines changed: 555 additions & 0 deletions

File tree

service/functionalTests/client.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,86 @@ export class MageClientSession {
228228
return this.http.get<Observation>(`/api/events/${eventId}/observations/${observationId}`).then(x => x.data)
229229
}
230230

231+
favoriteObservation(eventId: MageEventId, observationId: ObservationId): Promise<AxiosResponse<Observation>> {
232+
return this.http.put(`/api/events/${eventId}/observations/${observationId}/favorite`)
233+
}
234+
235+
unfavoriteObservation(eventId: MageEventId, observationId: ObservationId): Promise<AxiosResponse<Observation>> {
236+
return this.http.delete(`/api/events/${eventId}/observations/${observationId}/favorite`)
237+
}
238+
239+
markImportant(eventId: MageEventId, observationId: ObservationId, description?: string): Promise<AxiosResponse<Observation>> {
240+
return this.http.put(`/api/events/${eventId}/observations/${observationId}/important`, description ? { description } : {})
241+
}
242+
243+
unmarkImportant(eventId: MageEventId, observationId: ObservationId): Promise<AxiosResponse<Observation>> {
244+
return this.http.delete(`/api/events/${eventId}/observations/${observationId}/important`)
245+
}
246+
247+
addObservationState(eventId: MageEventId, observationId: ObservationId, name: 'active' | 'archive'): Promise<AxiosResponse<ObservationState>> {
248+
return this.http.post(`/api/events/${eventId}/observations/${observationId}/states`, { name })
249+
}
250+
251+
listUsers(query?: Record<string, string>): Promise<AxiosResponse<User[]>> {
252+
return this.http.get('/api/users', { params: query })
253+
}
254+
255+
getUser(userId: UserId): Promise<AxiosResponse<User>> {
256+
return this.http.get(`/api/users/${userId}`)
257+
}
258+
259+
updateUser(userId: UserId, attrs: Partial<UserCreateRequest>): Promise<AxiosResponse<User>> {
260+
const form = new FormData()
261+
for (const [key, val] of Object.entries(attrs)) {
262+
if (val != null) {
263+
form.set(key, String(val))
264+
}
265+
}
266+
return this.http.put(`/api/users/${userId}`, form)
267+
}
268+
269+
getMyself(): Promise<AxiosResponse<User>> {
270+
return this.http.get('/api/users/myself')
271+
}
272+
273+
updateMyself(attrs: Partial<Pick<User, 'displayName' | 'email'>>): Promise<AxiosResponse<User>> {
274+
const form = new FormData()
275+
for (const [key, val] of Object.entries(attrs)) {
276+
if (val != null) {
277+
form.set(key, String(val))
278+
}
279+
}
280+
return this.http.put('/api/users/myself', form)
281+
}
282+
283+
createTeam(attrs: TeamCreateRequest): Promise<AxiosResponse<Team>> {
284+
return this.http.post('/api/teams', attrs)
285+
}
286+
287+
listTeams(query?: Record<string, string>): Promise<AxiosResponse<Team[]>> {
288+
return this.http.get('/api/teams', { params: query })
289+
}
290+
291+
getTeam(teamId: Team['id']): Promise<AxiosResponse<Team>> {
292+
return this.http.get(`/api/teams/${teamId}`)
293+
}
294+
295+
updateTeam(teamId: Team['id'], attrs: Partial<TeamCreateRequest>): Promise<AxiosResponse<Team>> {
296+
return this.http.put(`/api/teams/${teamId}`, attrs)
297+
}
298+
299+
deleteTeam(teamId: Team['id']): Promise<AxiosResponse<Team>> {
300+
return this.http.delete(`/api/teams/${teamId}`)
301+
}
302+
303+
addTeamMember(teamId: Team['id'], userId: UserId): Promise<AxiosResponse<Team>> {
304+
return this.http.post(`/api/teams/${teamId}/users`, { id: userId })
305+
}
306+
307+
removeTeamMember(teamId: Team['id'], userId: UserId): Promise<AxiosResponse<Team>> {
308+
return this.http.delete(`/api/teams/${teamId}/users/${userId}`)
309+
}
310+
231311
postUserLocations(eventId: number, locations: Array<[lon: number, lat: number, timestamp?: number | undefined]>): Promise<AxiosResponse<UserLocation[]>> {
232312
const features = locations.map<geojson.Feature<geojson.Point>>(([lon, lat, timestamp]) => {
233313
if (typeof timestamp !== 'number') {
@@ -675,6 +755,18 @@ export interface Attachment {
675755
url?: string
676756
}
677757

758+
export interface ObservationState {
759+
name: 'active' | 'archive'
760+
userId?: UserId
761+
timestamp?: ISODateString
762+
}
763+
764+
export interface TeamCreateRequest {
765+
name: string
766+
description?: string
767+
userIds?: UserId[]
768+
}
769+
678770
export interface ObservationImportantFlag {
679771
userId?: UserId
680772
user?: UserLite
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
2+
import { expect } from 'chai'
3+
import {
4+
MageClientSession,
5+
RootUserSetupRequest,
6+
MageEventCreateRequest,
7+
MageFormCreateRequest,
8+
MageEventPopulated,
9+
MageForm,
10+
Observation,
11+
FormFieldType,
12+
} from '../client'
13+
14+
export const rootSeed: RootUserSetupRequest = {
15+
username: 'obs.root',
16+
displayName: 'Observations Root',
17+
password: 'obs.root.secret',
18+
uid: 'obs.root.device',
19+
}
20+
21+
export const eventSeed: MageEventCreateRequest = {
22+
name: 'Observation Test Event',
23+
style: {},
24+
}
25+
26+
export const formSeed: MageFormCreateRequest = {
27+
name: 'obs_form',
28+
userFields: [],
29+
archived: false,
30+
color: '#aa0000',
31+
fields: [
32+
{
33+
id: 1,
34+
name: 'title',
35+
required: false,
36+
title: 'Title',
37+
type: FormFieldType.Text,
38+
},
39+
],
40+
}
41+
42+
export interface ObservationFixture {
43+
event: MageEventPopulated
44+
form: MageForm
45+
observation: Observation
46+
}
47+
48+
export async function populateFixtureData(rootSession: MageClientSession): Promise<ObservationFixture> {
49+
50+
const event = await rootSession.createEvent(eventSeed).then(x => x.data)
51+
52+
expect(event.id).to.be.a('number')
53+
54+
const form = await rootSession.createForm(event.id, formSeed).then(x => x.data)
55+
const eventWithForms = await rootSession.readEvent(event.id).then(x => x.data)
56+
await rootSession.addParticipantToEvent(eventWithForms, rootSession.user!.id)
57+
58+
const observation = await rootSession.saveObservation({
59+
id: null,
60+
type: 'Feature',
61+
geometry: { type: 'Point', coordinates: [-104.9352, 39.6285] },
62+
eventId: event.id,
63+
properties: {
64+
timestamp: new Date().toISOString(),
65+
forms: [{ formId: form.id, title: 'Test Observation' }],
66+
},
67+
})
68+
69+
expect(observation.id).to.be.a('string')
70+
71+
return { event: eventWithForms, form, observation }
72+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
2+
// list observations in event
3+
// read observation by id
4+
// favorite an observation
5+
// unfavorite an observation
6+
// mark observation as important
7+
// unmark observation as important
8+
// transition observation state to archived
9+
// transition observation state to active
10+
11+
import { expect } from 'chai'
12+
import { MageClientSession, SignInResult } from '../client'
13+
import { ChildProcessTestStackRef, launchTestStack } from '../stack'
14+
import * as Fixture from './fixture'
15+
16+
17+
describe('observations', function () {
18+
19+
let stack: ChildProcessTestStackRef
20+
let rootSession: MageClientSession
21+
let fixture: Fixture.ObservationFixture
22+
23+
this.timeout(15000)
24+
25+
before('initialize stack', async function () {
26+
stack = await launchTestStack('observations')
27+
})
28+
29+
before('initialize fixture data', async function () {
30+
31+
rootSession = new MageClientSession(stack.mageUrl)
32+
const rootSetup = await rootSession.setupRootUser(Fixture.rootSeed).then(x => x.data)
33+
34+
expect(rootSetup.user.username).to.equal(Fixture.rootSeed.username)
35+
expect(rootSetup.device.uid).to.equal(Fixture.rootSeed.uid)
36+
37+
const rootSignIn = await rootSession.signIn(Fixture.rootSeed.username, Fixture.rootSeed.password, Fixture.rootSeed.uid) as SignInResult
38+
39+
expect(rootSignIn.user).to.exist
40+
expect(rootSignIn.user.username).to.equal(Fixture.rootSeed.username)
41+
42+
fixture = await Fixture.populateFixtureData(rootSession)
43+
})
44+
45+
after('stop stack', async function () {
46+
await stack.stop()
47+
})
48+
49+
it('lists observations in event', async function () {
50+
const observations = await rootSession.readObservations(fixture.event.id)
51+
52+
expect(observations).to.be.an('array').with.length.greaterThan(0)
53+
const found = observations.find(o => o.id === fixture.observation.id)
54+
expect(found, 'fixture observation not in list').to.exist
55+
})
56+
57+
it('reads observation by id', async function () {
58+
const obs = await rootSession.readObservation(fixture.event.id, fixture.observation.id)
59+
60+
expect(obs.id).to.equal(fixture.observation.id)
61+
expect(obs.eventId).to.equal(fixture.event.id)
62+
expect(obs.geometry.type).to.equal('Point')
63+
})
64+
65+
it('favorites and unfavorites an observation', async function () {
66+
const { event, observation } = fixture
67+
68+
const favorited = await rootSession.favoriteObservation(event.id, observation.id).then(x => x.data)
69+
expect(favorited.favoriteUserIds).to.include(rootSession.user!.id)
70+
71+
const unfavorited = await rootSession.unfavoriteObservation(event.id, observation.id).then(x => x.data)
72+
expect(unfavorited.favoriteUserIds).not.to.include(rootSession.user!.id)
73+
})
74+
75+
it('marks and unmarks an observation as important', async function () {
76+
const { event, observation } = fixture
77+
const description = 'High priority incident'
78+
79+
const marked = await rootSession.markImportant(event.id, observation.id, description).then(x => x.data)
80+
expect(marked.important).to.exist
81+
expect(marked.important!.description).to.equal(description)
82+
83+
const unmarked = await rootSession.unmarkImportant(event.id, observation.id).then(x => x.data)
84+
expect(unmarked.important).not.to.exist
85+
})
86+
87+
it('adds observation state transitions', async function () {
88+
const { event, observation } = fixture
89+
90+
const archived = await rootSession.addObservationState(event.id, observation.id, 'archive').then(x => x.data)
91+
expect(archived.name).to.equal('archive')
92+
93+
const reactivated = await rootSession.addObservationState(event.id, observation.id, 'active').then(x => x.data)
94+
expect(reactivated.name).to.equal('active')
95+
})
96+
})
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
2+
import { expect } from 'chai'
3+
import {
4+
MageClientSession,
5+
RootUserSetupRequest,
6+
UserCreateRequest,
7+
User,
8+
Team,
9+
TeamCreateRequest,
10+
} from '../client'
11+
12+
export const rootSeed: RootUserSetupRequest = {
13+
username: 'teams.root',
14+
displayName: 'Teams Root',
15+
password: 'teams.root.secret',
16+
uid: 'teams.root.device',
17+
}
18+
19+
export const userSeed: Omit<UserCreateRequest, 'roleId'> = {
20+
username: 'teams.testuser',
21+
displayName: 'Teams Test User',
22+
password: 'testuser.secret_password',
23+
}
24+
25+
export const teamSeed: TeamCreateRequest = {
26+
name: 'Fixture Team',
27+
description: 'Created by functional test fixture',
28+
}
29+
30+
export interface TeamsFixture {
31+
additionalUser: User
32+
team: Team
33+
}
34+
35+
export async function populateFixtureData(rootSession: MageClientSession): Promise<TeamsFixture> {
36+
37+
const roles = await rootSession.listRoles().then(x => x.data)
38+
const userRole = roles.find(x => x.name === 'USER_ROLE')!
39+
40+
expect(userRole, 'failed to find user role').to.exist
41+
42+
const additionalUser = await rootSession.createUser({
43+
...userSeed,
44+
roleId: userRole.id,
45+
}).then(x => x.data)
46+
47+
expect(additionalUser.id, 'additional user missing id').to.be.a('string')
48+
49+
const team = await rootSession.createTeam(teamSeed).then(x => x.data)
50+
51+
expect(team.id, 'team missing id').to.be.a('string')
52+
expect(team.name).to.equal(teamSeed.name)
53+
54+
return { additionalUser, team }
55+
}

0 commit comments

Comments
 (0)