From 8bf34dc2bd63af11f4f56234a4b96bd1218c946e Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Fri, 22 Nov 2024 18:31:00 +0200 Subject: [PATCH] Test that only one create_service_install task is created per PATCH Change-type: patch --- test/25_service-installs.ts | 63 +++++++++++++++++++++++++ test/test-lib/api-helpers.ts | 89 +++++++++++++++++++++++++++++++++++- 2 files changed, 150 insertions(+), 2 deletions(-) diff --git a/test/25_service-installs.ts b/test/25_service-installs.ts index 6638571bb..45b26ac4c 100644 --- a/test/25_service-installs.ts +++ b/test/25_service-installs.ts @@ -8,6 +8,7 @@ import * as fixtures from './test-lib/fixtures.js'; import { assertExists, expectToEventually } from './test-lib/common.js'; import * as config from '../src/lib/config.js'; import { supertest } from './test-lib/supertest.js'; +import { expectNewTasks, resetLatestTaskIds } from './test-lib/api-helpers.js'; export default () => { versions.test((version, pineTest) => { @@ -45,6 +46,8 @@ export default () => { config.TEST_MOCK_ONLY.ASYNC_TASK_CREATE_SERVICE_INSTALLS_ENABLED = isServiceInstallEnabled; + + await resetLatestTaskIds('create_service_installs'); }); after(async () => { @@ -74,6 +77,19 @@ export default () => { ctx.app1Service1.id, ); }); + await expectNewTasks( + 'create_service_installs', + isServiceInstallEnabled + ? [ + { + is_executed_with__parameter_set: { + devices: [ctx.device.id], + }, + status: 'succeeded', + }, + ] + : [], + ); }); it('for pinning an application to a release', async () => { @@ -104,6 +120,19 @@ export default () => { ctx.app1Service2.id, ); }); + await expectNewTasks( + 'create_service_installs', + isServiceInstallEnabled + ? [ + { + is_executed_with__parameter_set: { + devices: [ctx.device.id], + }, + status: 'succeeded', + }, + ] + : [], + ); }); it('when a device is pinned on a different release', async () => { @@ -133,6 +162,19 @@ export default () => { ctx.app1Service3.id, ); }); + await expectNewTasks( + 'create_service_installs', + isServiceInstallEnabled + ? [ + { + is_executed_with__parameter_set: { + devices: [ctx.device.id], + }, + status: 'succeeded', + }, + ] + : [], + ); }); it('when device is moved to different application', async () => { @@ -170,6 +212,27 @@ export default () => { ctx.app2Service1.id, ); }); + await expectNewTasks( + 'create_service_installs', + isServiceInstallEnabled + ? [ + // the first one is from unpinning + { + is_executed_with__parameter_set: { + devices: [ctx.device.id], + }, + status: 'succeeded', + }, + // the second one is from moving application + { + is_executed_with__parameter_set: { + devices: [ctx.device.id], + }, + status: 'succeeded', + }, + ] + : [], + ); }); it('should be able to use service_install to create a device_service_environment_variable', async () => { diff --git a/test/test-lib/api-helpers.ts b/test/test-lib/api-helpers.ts index 1d75311ac..6349fef0e 100644 --- a/test/test-lib/api-helpers.ts +++ b/test/test-lib/api-helpers.ts @@ -7,7 +7,9 @@ import type { UserObjectParam } from '../test-lib/supertest.js'; import { supertest } from '../test-lib/supertest.js'; import type { TokenUserPayload } from '../../src/index.js'; import type { RequiredField } from '@balena/pinejs/out/sbvr-api/common-types.js'; -import { assertExists } from './common.js'; +import { assertExists, expectToEventually } from './common.js'; +import { sbvrUtils, permissions } from '@balena/pinejs'; +import type { tasks } from '@balena/pinejs'; const version = 'resin'; @@ -208,7 +210,7 @@ export const thatIsDateStringAfter = ( const validJwtProps = ['id', 'jwt_secret', 'authTime', 'iat', 'exp'].sort(); -export function expectJwt(tokenOrJwt: string | AnyObject) { +export function expectJwt(tokenOrJwt: string | object) { const decoded = ( typeof tokenOrJwt === 'string' ? jsonwebtoken.decode(tokenOrJwt) @@ -232,3 +234,86 @@ export function expectJwt(tokenOrJwt: string | AnyObject) { return decoded; } + +export type TaskExpectation = Pick< + tasks.Task['Read'], + 'is_executed_with__parameter_set' | 'status' +>; + +const expectTasks = async ( + handler: string, + expectedTasks: TaskExpectation[], + lastId = '0', +) => { + const tasks = await sbvrUtils.api.tasks.get({ + resource: 'task', + passthrough: { req: permissions.rootRead }, + options: { + $select: ['id', 'is_executed_with__parameter_set', 'status'], + $filter: { + id: { $gt: lastId }, + is_executed_by__handler: handler, + }, + $orderby: { id: 'asc' }, + }, + }); + const actual = tasks.map((t) => + _.pick(t, ['is_executed_with__parameter_set', 'status']), + ); + + expect(actual).to.deep.equal(expectedTasks); + return tasks; +}; + +const latestTaskIdByHandler: Dictionary = {}; + +export const expectNewTasks = async ( + handler: string, + expectedUsage: TaskExpectation[], +) => { + const tasks = await expectTasks( + handler, + expectedUsage, + latestTaskIdByHandler[handler], + ); + const lastTaskId = tasks.at(-1)?.id; + if (lastTaskId != null) { + latestTaskIdByHandler[handler] = lastTaskId; + } +}; + +export const resetLatestTaskIds = async (handler: string) => { + await expectToEventually(async () => { + const runningTasks = await sbvrUtils.api.tasks.get({ + resource: 'task', + passthrough: { req: permissions.rootRead }, + options: { + $top: 1, + $select: 'id', + $filter: { + status: 'queued', + is_executed_by__handler: handler, + }, + $orderby: { id: 'desc' }, + }, + }); + expect(runningTasks).to.have.lengthOf(0); + }); + const [latestTask] = await sbvrUtils.api.tasks.get({ + resource: 'task', + passthrough: { req: permissions.rootRead }, + options: { + $top: 1, + $select: 'id', + $filter: { + status: 'succeeded', + is_executed_by__handler: handler, + }, + $orderby: { id: 'desc' }, + }, + }); + if (latestTask == null) { + return; + } + latestTaskIdByHandler[handler] = latestTask?.id; +};