From 56a8d78d6bc3980982bf3918de9abad30b95a4cb Mon Sep 17 00:00:00 2001 From: ElderMatt <18527012+ElderMatt@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:03:10 +0100 Subject: [PATCH 1/4] fix: add checks for undefined clustername --- src/otomi-stack.test.ts | 45 ++++++++++++++++++++++++++++++++++++++++ src/otomi-stack.ts | 19 ++++++++--------- src/utils/wizardUtils.ts | 11 +++++++++- 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/src/otomi-stack.test.ts b/src/otomi-stack.test.ts index bd886589a..cacab2d6c 100644 --- a/src/otomi-stack.test.ts +++ b/src/otomi-stack.test.ts @@ -4,6 +4,7 @@ import { AplServiceRequest, AplTeamSettingsRequest, App, + ObjWizard, SessionUser, User, } from 'src/otomi-models' @@ -11,6 +12,7 @@ import OtomiStack from 'src/otomi-stack' import { loadSpec } from './app' import { PublicUrlExists, ValidationError } from './error' import { Git } from './git' +import { defineClusterId } from './utils/wizardUtils' jest.mock('src/middleware', () => ({ ...jest.requireActual('src/middleware'), @@ -930,7 +932,50 @@ describe('PodService', () => { }) }) }) +describe('Wizard tests', () => { + let otomiStack: OtomiStack + const domainSuffix = 'dev.linode-apl.net' + beforeEach(async () => { + otomiStack = new OtomiStack() + await otomiStack.init() + }) + afterEach(() => { + jest.restoreAllMocks() + }) + + test('should return lkeClusterId', () => { + const settings = { cluster: { name: 'cluster-123' } } + const clusterId = defineClusterId(settings.cluster.name) + + expect(clusterId).toBe('cluster-123') + }) + + test('should return stripped down clusterId when name does not include prefix', () => { + const settings = { cluster: { name: 'aplinstall123' } } + const clusterId = defineClusterId(settings.cluster.name) + + expect(clusterId).toBe('123') + }) + + test('should return undefined when cluster name is undefined', () => { + const settings: any = { cluster: {} } + const clusterId = defineClusterId(settings.cluster?.name) + + expect(clusterId).toBe(undefined) + }) + + test('should return error when cluster name is undefined', async () => { + const data = { apiToken: 'some-token', regionId: 'us-east', label: 'my-cluster' } + jest.spyOn(otomiStack, 'getSettings').mockReturnValue({ + cluster: { domainSuffix, provider: 'linode' }, + } as any) + const result: ObjWizard = await otomiStack.createObjWizard(data) + + expect(result.status).toBe('error') + expect(result.errorMessage).toBe('Cluster name is not found.') + }) +}) describe('Code repositories tests', () => { let otomiStack: OtomiStack let mockGit: jest.Mocked diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index f719d85cf..0fa4d4851 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -1,4 +1,4 @@ -import { CoreV1Api, KubeConfig, User as k8sUser, V1ObjectReference } from '@kubernetes/client-node' +import { CoreV1Api, User as k8sUser, KubeConfig, V1ObjectReference } from '@kubernetes/client-node' import Debug from 'debug' import { getRegions, ObjectStorageKeyRegions } from '@linode/api-v4' @@ -17,9 +17,9 @@ import { PublicUrlExists, ValidationError, } from 'src/error' -import getRepo, { getWorktreeRepo, Git } from 'src/git' -import { FileStore } from 'src/fileStore/file-store' import { getSettingsFileMaps } from 'src/fileStore/file-map' +import { FileStore } from 'src/fileStore/file-store' +import getRepo, { getWorktreeRepo, Git } from 'src/git' import { cleanSession, getSessionStack } from 'src/middleware' import { AplAgentRequest, @@ -109,6 +109,7 @@ import { v4 as uuidv4 } from 'uuid' import { parse as parseYaml, stringify as stringifyYaml } from 'yaml' import { getAIModels } from './ai/aiModelHandler' import { DatabaseCR } from './ai/DatabaseCR' +import { getResourceFilePath, getSecretFilePath } from './fileStore/file-map' import { apply, checkPodExists, @@ -130,9 +131,8 @@ import { import { getAplObjectFromV1, getV1MergeObject, getV1ObjectFromApl } from './utils/manifests' import { getSealedSecretsPEM, sealedSecretManifest } from './utils/sealedSecretUtils' import { getKeycloakUsers, isValidUsername } from './utils/userUtils' -import { ObjectStorageClient } from './utils/wizardUtils' +import { defineClusterId, ObjectStorageClient } from './utils/wizardUtils' import { fetchChartYaml, fetchWorkloadCatalog, NewHelmChartValues, sparseCloneChart } from './utils/workloadUtils' -import { getResourceFilePath, getSecretFilePath } from './fileStore/file-map' interface ExcludedApp extends App { managed: boolean @@ -301,12 +301,11 @@ export default class OtomiStack { const createdBuckets = [] as Array if (data?.apiToken && data?.regionId) { const { cluster } = this.getSettings(['cluster']) - let lkeClusterId: null | number = null - if (cluster?.name?.includes('aplinstall')) { - lkeClusterId = Number(cluster?.name?.replace('aplinstall', '')) - } else if (lkeClusterId === null) { - return { status: 'error', errorMessage: 'Cluster ID is not found in the cluster name.' } + let lkeClusterId: undefined | string = defineClusterId(cluster?.name) + if (lkeClusterId === undefined) { + return { status: 'error', errorMessage: 'Cluster name is not found.' } } + const bucketNames = { cnpg: `lke${lkeClusterId}-cnpg`, harbor: `lke${lkeClusterId}-harbor`, diff --git a/src/utils/wizardUtils.ts b/src/utils/wizardUtils.ts index 669444267..2878da205 100644 --- a/src/utils/wizardUtils.ts +++ b/src/utils/wizardUtils.ts @@ -28,7 +28,7 @@ export class ObjectStorageClient { } public async createObjectStorageKey( - lkeClusterId: number, + lkeClusterId: string, region: string, bucketNames: string[], ): Promise | OtomiError> { @@ -56,3 +56,12 @@ export class ObjectStorageClient { } } } +// define cluster id based on cluster name +export function defineClusterId(clusterName: string | undefined): string | undefined { + if (!clusterName) return undefined + if (clusterName.includes('aplinstall')) { + return clusterName.replace('aplinstall', '') + } else { + return clusterName + } +} From 335c9890ddcacc1ee2ffd5a58356f9ed33ecc511 Mon Sep 17 00:00:00 2001 From: ElderMatt <18527012+ElderMatt@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:10:48 +0100 Subject: [PATCH 2/4] fix: move tests to wizard test file --- src/otomi-stack.test.ts | 47 +------------------------------- src/utils/wizardUtils.test.ts | 51 ++++++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 50 deletions(-) diff --git a/src/otomi-stack.test.ts b/src/otomi-stack.test.ts index cacab2d6c..d007b0823 100644 --- a/src/otomi-stack.test.ts +++ b/src/otomi-stack.test.ts @@ -4,15 +4,13 @@ import { AplServiceRequest, AplTeamSettingsRequest, App, - ObjWizard, SessionUser, - User, + User } from 'src/otomi-models' import OtomiStack from 'src/otomi-stack' import { loadSpec } from './app' import { PublicUrlExists, ValidationError } from './error' import { Git } from './git' -import { defineClusterId } from './utils/wizardUtils' jest.mock('src/middleware', () => ({ ...jest.requireActual('src/middleware'), @@ -932,50 +930,7 @@ describe('PodService', () => { }) }) }) -describe('Wizard tests', () => { - let otomiStack: OtomiStack - const domainSuffix = 'dev.linode-apl.net' - beforeEach(async () => { - otomiStack = new OtomiStack() - await otomiStack.init() - }) - afterEach(() => { - jest.restoreAllMocks() - }) - - test('should return lkeClusterId', () => { - const settings = { cluster: { name: 'cluster-123' } } - const clusterId = defineClusterId(settings.cluster.name) - - expect(clusterId).toBe('cluster-123') - }) - - test('should return stripped down clusterId when name does not include prefix', () => { - const settings = { cluster: { name: 'aplinstall123' } } - const clusterId = defineClusterId(settings.cluster.name) - - expect(clusterId).toBe('123') - }) - - test('should return undefined when cluster name is undefined', () => { - const settings: any = { cluster: {} } - const clusterId = defineClusterId(settings.cluster?.name) - - expect(clusterId).toBe(undefined) - }) - - test('should return error when cluster name is undefined', async () => { - const data = { apiToken: 'some-token', regionId: 'us-east', label: 'my-cluster' } - jest.spyOn(otomiStack, 'getSettings').mockReturnValue({ - cluster: { domainSuffix, provider: 'linode' }, - } as any) - const result: ObjWizard = await otomiStack.createObjWizard(data) - - expect(result.status).toBe('error') - expect(result.errorMessage).toBe('Cluster name is not found.') - }) -}) describe('Code repositories tests', () => { let otomiStack: OtomiStack let mockGit: jest.Mocked diff --git a/src/utils/wizardUtils.test.ts b/src/utils/wizardUtils.test.ts index 9c8388d06..4c2eeaf46 100644 --- a/src/utils/wizardUtils.test.ts +++ b/src/utils/wizardUtils.test.ts @@ -15,11 +15,13 @@ jest.mock('@linode/api-v4', () => ({ import { createBucket, createObjectStorageKeys, ObjectStorageKey, setToken } from '@linode/api-v4' import { OtomiError } from 'src/error' -import { ObjectStorageClient } from './wizardUtils' +import { ObjWizard } from 'src/otomi-models' +import OtomiStack from 'src/otomi-stack' +import { defineClusterId, ObjectStorageClient } from './wizardUtils' describe('ObjectStorageClient', () => { let client: ObjectStorageClient - const clusterId = 12345 + let clusterId: string | undefined = '12345' beforeEach(() => { client = new ObjectStorageClient('test-token') @@ -35,7 +37,16 @@ describe('ObjectStorageClient', () => { describe('createObjectStorageBucket', () => { const label = 'test-bucket' const region = 'us-east' + let otomiStack: OtomiStack + const domainSuffix = 'dev.linode-apl.net' + beforeEach(async () => { + otomiStack = new OtomiStack() + await otomiStack.init() + }) + afterEach(() => { + jest.restoreAllMocks() + }) it('should successfully create bucket', async () => { const mockResponse = { label: 'test-bucket' } ;(createBucket as jest.Mock).mockResolvedValue(mockResponse) @@ -81,6 +92,38 @@ describe('ObjectStorageClient', () => { // @ts-ignore expect(result.code).toBe(500) }) + + test('should return lkeClusterId', () => { + const settings = { cluster: { name: 'cluster-123' } } + clusterId = defineClusterId(settings.cluster.name) + + expect(clusterId).toBe('cluster-123') + }) + + test('should return stripped down clusterId when name does not include prefix', () => { + const settings = { cluster: { name: 'aplinstall123' } } + clusterId = defineClusterId(settings.cluster.name) + + expect(clusterId).toBe('123') + }) + + test('should return undefined when cluster name is undefined', () => { + const settings: any = { cluster: {} } + clusterId = defineClusterId(settings.cluster?.name) + + expect(clusterId).toBe(undefined) + }) + + test('should return error when cluster name is undefined', async () => { + const data = { apiToken: 'some-token', regionId: 'us-east', label: 'my-cluster' } + jest.spyOn(otomiStack, 'getSettings').mockReturnValue({ + cluster: { domainSuffix, provider: 'linode' }, + } as any) + const result: ObjWizard = await otomiStack.createObjWizard(data) + + expect(result.status).toBe('error') + expect(result.errorMessage).toBe('Cluster name is not found.') + }) }) describe('createObjectStorageKey', () => { @@ -103,7 +146,7 @@ describe('ObjectStorageClient', () => { } ;(createObjectStorageKeys as jest.Mock).mockResolvedValue(mockResponse) - const result = await client.createObjectStorageKey(clusterId, region, bucketNames) + const result = await client.createObjectStorageKey(clusterId!, region, bucketNames) expect(createObjectStorageKeys).toHaveBeenCalledWith({ label: `lke${clusterId}-key-1704110400000`, @@ -125,7 +168,7 @@ describe('ObjectStorageClient', () => { } ;(createObjectStorageKeys as jest.Mock).mockRejectedValue(mockError) - const result = await client.createObjectStorageKey(clusterId, region, bucketNames) + const result = await client.createObjectStorageKey(clusterId!, region, bucketNames) expect(result).toBeInstanceOf(OtomiError) // @ts-ignore expect(result.publicMessage).toBe('Your OAuth token is not authorized to use this endpoint') From 1ba0fa33cacbaa995f74f8994418f284afd16712 Mon Sep 17 00:00:00 2001 From: ElderMatt <18527012+ElderMatt@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:14:44 +0100 Subject: [PATCH 3/4] fix: lint --- src/otomi-stack.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/otomi-stack.test.ts b/src/otomi-stack.test.ts index d007b0823..bd886589a 100644 --- a/src/otomi-stack.test.ts +++ b/src/otomi-stack.test.ts @@ -5,7 +5,7 @@ import { AplTeamSettingsRequest, App, SessionUser, - User + User, } from 'src/otomi-models' import OtomiStack from 'src/otomi-stack' import { loadSpec } from './app' From 3908cc5cff2ba063101044e650780db9abad50a8 Mon Sep 17 00:00:00 2001 From: ElderMatt <18527012+ElderMatt@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:48:28 +0100 Subject: [PATCH 4/4] fix: remove redundant if else --- src/utils/wizardUtils.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/utils/wizardUtils.ts b/src/utils/wizardUtils.ts index 2878da205..57c448b80 100644 --- a/src/utils/wizardUtils.ts +++ b/src/utils/wizardUtils.ts @@ -59,9 +59,5 @@ export class ObjectStorageClient { // define cluster id based on cluster name export function defineClusterId(clusterName: string | undefined): string | undefined { if (!clusterName) return undefined - if (clusterName.includes('aplinstall')) { - return clusterName.replace('aplinstall', '') - } else { - return clusterName - } + return clusterName.replace('aplinstall', '') }