Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
003f464
fix: add query to findRobotByName
CasLubbers Mar 2, 2026
efdcb81
fix: add query to findRobotByName and update logging
CasLubbers Mar 2, 2026
d4406d1
fix: add query to findRobotByName and update logging
CasLubbers Mar 2, 2026
9da8a0f
fix: change to debug
CasLubbers Mar 2, 2026
38986f6
fix: add reconciler loop
CasLubbers Mar 2, 2026
cf80365
fix: add reconciler loop and manage project
CasLubbers Mar 2, 2026
9ed6110
fix: tests
CasLubbers Mar 2, 2026
2bed006
Merge remote-tracking branch 'origin/main' into APL-1417-refactoring-9
CasLubbers Mar 2, 2026
75cb80a
fix: set robot account level system
CasLubbers Mar 2, 2026
4a04ba7
fix: set robot account level system in tests
CasLubbers Mar 2, 2026
2e24d44
fix: use createbuildssecret instead of createsecret
ElderMatt Mar 3, 2026
99f4c8d
fix: tests
ElderMatt Mar 3, 2026
62fe9b2
fix: secret distinction between builds and pull push
ElderMatt Mar 3, 2026
efebc83
fix: add createHarborTeamSecret function
CasLubbers Mar 3, 2026
f05a0d4
fix(wip): fix secret of robot account
CasLubbers Mar 3, 2026
178ce61
fix: refresh secret after creating or updating robot account
CasLubbers Mar 3, 2026
4125cb5
fix: use library for password generation
CasLubbers Mar 3, 2026
4139f9e
fix: add comment to password generation
CasLubbers Mar 4, 2026
39e5f23
test: leverage runAllTimers to improve test execution performance
j-zimnowoda Mar 4, 2026
01416f0
test: test coverage for the parseDockerConfigJson function
j-zimnowoda Mar 4, 2026
5467135
Merge remote-tracking branch 'origin/APL-1417-refactoring-9' into APL…
j-zimnowoda Mar 4, 2026
db73720
fix: comments
j-zimnowoda Mar 4, 2026
cbc2ae8
fix: http error handling
j-zimnowoda Mar 4, 2026
a4d61d1
fix: clean errors array
j-zimnowoda Mar 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 72 additions & 23 deletions src/operators/harbor/harbor.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ProjectReq, RobotCreate, RobotCreated } from '@linode/harbor-client-node'
import * as k8s from '../../k8s'
import { __setApiClients, manageHarborProjectsAndRobotAccounts } from './harbor'
import { createRobotAccount, createSystemRobotSecret, ensureRobotAccount } from './lib/managers/harbor-robots'
import manageHarborProjectsAndRobotAccounts from './harbor'
import { createSystemRobotSecret, creatingRobotAccount, ensureRobotAccount } from './lib/managers/harbor-robots'

jest.mock('@kubernetes/client-node', () => ({
KubeConfig: jest.fn().mockImplementation(() => ({
Expand Down Expand Up @@ -59,6 +59,7 @@ describe('harborOperator', () => {
deleteRobot: jest.fn(),
updateRobot: jest.fn(),
setDefaultAuthentication: jest.fn(),
refreshSec: jest.fn(),
}

const mockConfigureApi = {
Expand All @@ -69,11 +70,14 @@ describe('harborOperator', () => {
const mockProjectsApi = {
createProject: jest.fn(),
getProject: jest.fn(),
updateProject: jest.fn(),
setDefaultAuthentication: jest.fn(),
}

const mockMemberApi = {
createProjectMember: jest.fn(),
listProjectMembers: jest.fn(),
updateProjectMember: jest.fn(),
setDefaultAuthentication: jest.fn(),
}

Expand All @@ -93,20 +97,21 @@ describe('harborOperator', () => {
teamNamespaces: [],
}

const mockApis = {
robotApi: mockRobotApi,
configureApi: mockConfigureApi,
projectsApi: mockProjectsApi,
memberApi: mockMemberApi,
}

beforeEach(() => {
jest.clearAllMocks()

mockK8s.getSecret.mockResolvedValue(null)
mockK8s.createSecret.mockResolvedValue(undefined)
mockK8s.replaceSecret.mockResolvedValue(undefined)
mockK8s.createK8sSecret.mockResolvedValue(undefined)

__setApiClients(
mockRobotApi as any,
mockConfigureApi as any,
mockProjectsApi as any,
mockMemberApi as any,
)
mockK8s.createBuildsK8sSecret.mockResolvedValue(undefined)
})

afterEach(() => {
Expand Down Expand Up @@ -206,7 +211,7 @@ describe('harborOperator', () => {
it('should create a robot account successfully', async () => {
const projectRobot: RobotCreate = {
name: 'team-demo-pull',
level: 'project',
level: 'system',
duration: -1,
description: 'Allow to pull from project container registry',
disable: false,
Expand All @@ -227,24 +232,24 @@ describe('harborOperator', () => {

mockRobotApi.createRobot.mockResolvedValue({ body: mockRobotCreated })

const result = await createRobotAccount(projectRobot, mockRobotApi as any)
await creatingRobotAccount(projectRobot, mockRobotApi as any)

expect(result).toEqual(mockRobotCreated)
expect(mockRobotApi.createRobot).toHaveBeenCalledWith(projectRobot)
expect(mockRobotApi.refreshSec).toHaveBeenCalled()
})

it('should throw error if robot creation fails', async () => {
const projectRobot: RobotCreate = {
name: 'team-demo-pull',
level: 'project',
level: 'system',
duration: -1,
disable: false,
permissions: [],
}

mockRobotApi.createRobot.mockRejectedValue(new Error('Robot creation failed'))

await expect(createRobotAccount(projectRobot, mockRobotApi as any)).rejects.toThrow('Robot creation failed')
await expect(creatingRobotAccount(projectRobot, mockRobotApi as any)).rejects.toThrow('Robot creation failed')
})
})

Expand All @@ -260,13 +265,15 @@ describe('harborOperator', () => {
expect.objectContaining({
namespace: 'team-demo',
name: 'harbor-pullsecret',
username: 'team-demo-pull',
username: 'otomi-team-demo-pull',
server: 'harbor.example.com',
password: expect.any(String),
}),
)
expect(mockRobotApi.createRobot).toHaveBeenCalledWith(
expect.objectContaining({
name: 'team-demo-pull',
level: 'project',
level: 'system',
permissions: expect.arrayContaining([
expect.objectContaining({
kind: 'project',
Expand Down Expand Up @@ -384,27 +391,65 @@ describe('harborOperator', () => {
secret: 'robot-secret-123',
}

mockProjectsApi.createProject.mockResolvedValue({})
mockProjectsApi.getProject.mockResolvedValue({ body: mockProject })
mockProjectsApi.createProject.mockResolvedValue({body: {
projectId: 1
}})
mockProjectsApi.getProject.mockRejectedValue({body: {
errors : [{ code: 'PROJECT_NOT_FOUND', message: 'Project not found' }],
}})
mockMemberApi.listProjectMembers.mockResolvedValue({
body: [],
})
mockMemberApi.createProjectMember.mockResolvedValue({})
mockRobotApi.listRobot.mockResolvedValue({ body: [] })
mockRobotApi.createRobot.mockResolvedValue({ body: mockRobotCreated })

const result = await manageHarborProjectsAndRobotAccounts(namespace)
const result = await manageHarborProjectsAndRobotAccounts(namespace, mockHarborConfig as any, mockApis as any)

expect(mockProjectsApi.createProject).toHaveBeenCalledWith(mockProjectReq)
expect(mockProjectsApi.getProject).toHaveBeenCalledWith(namespace)
expect(mockMemberApi.createProjectMember).toHaveBeenCalledTimes(2)
expect(result).toBe('1')
})
it('should update project members if they exist already, associate team roles, and set up robot accounts', async () => {
const namespace = 'team-demo'
const mockProject = { projectId: 1, name: namespace }
const mockProjectReq: ProjectReq = { projectName: namespace }
const mockRobotCreated: RobotCreated = {
id: 1,
name: 'otomi-team-demo-pull',
secret: 'robot-secret-123',
}

mockProjectsApi.createProject.mockResolvedValue({body: {
projectId: 1
}})
mockProjectsApi.getProject.mockRejectedValue({body: {
errors : [{ code: 'PROJECT_NOT_FOUND', message: 'Project not found' }],
}})
mockMemberApi.listProjectMembers.mockResolvedValue({
body: [{ id: 1, entityName: 'team-demo', roleId: 2 }],
})
mockMemberApi.createProjectMember.mockResolvedValue({})
mockRobotApi.listRobot.mockResolvedValue({ body: [] })
mockRobotApi.createRobot.mockResolvedValue({ body: mockRobotCreated })

const result = await manageHarborProjectsAndRobotAccounts(namespace, mockHarborConfig as any, mockApis as any)

expect(mockProjectsApi.createProject).toHaveBeenCalledWith(mockProjectReq)
expect(mockProjectsApi.getProject).toHaveBeenCalledWith(namespace)
expect(mockMemberApi.updateProjectMember).toHaveBeenCalledTimes(2)
expect(mockMemberApi.createProjectMember).not.toHaveBeenCalled()
expect(result).toBe('1')
})

it('should return null if project not found', async () => {
const namespace = 'team-demo'

mockProjectsApi.createProject.mockResolvedValue({})
mockProjectsApi.getProject.mockResolvedValue({ body: null })

const result = await manageHarborProjectsAndRobotAccounts(namespace)
const result = await manageHarborProjectsAndRobotAccounts(namespace, mockHarborConfig as any, mockApis as any)

expect(result).toBeNull()
})
Expand All @@ -415,7 +460,7 @@ describe('harborOperator', () => {
mockProjectsApi.createProject.mockRejectedValue(new Error('Project creation failed'))
mockProjectsApi.getProject.mockResolvedValue({ body: null })

const result = await manageHarborProjectsAndRobotAccounts(namespace)
const result = await manageHarborProjectsAndRobotAccounts(namespace, mockHarborConfig as any, mockApis as any)

expect(result).toBe(null)
})
Expand All @@ -431,11 +476,15 @@ describe('harborOperator', () => {

mockProjectsApi.createProject.mockResolvedValue({})
mockProjectsApi.getProject.mockResolvedValue({ body: mockProject })
mockProjectsApi.updateProject.mockResolvedValue({ body: mockProject })
mockMemberApi.listProjectMembers.mockResolvedValue({
body: [],
})
mockMemberApi.createProjectMember.mockRejectedValue(new Error('Member creation failed'))
mockRobotApi.listRobot.mockResolvedValue({ body: [] })
mockRobotApi.createRobot.mockResolvedValue({ body: mockRobotCreated })

const result = await manageHarborProjectsAndRobotAccounts(namespace)
const result = await manageHarborProjectsAndRobotAccounts(namespace, mockHarborConfig as any, mockApis as any)

expect(mockMemberApi.createProjectMember).toHaveBeenCalled()
expect(result).toBe('1')
Expand All @@ -450,7 +499,7 @@ describe('harborOperator', () => {
mockMemberApi.createProjectMember.mockResolvedValue({})
mockRobotApi.listRobot.mockRejectedValue(new Error('Robot API error'))

const result = await manageHarborProjectsAndRobotAccounts(namespace)
const result = await manageHarborProjectsAndRobotAccounts(namespace, mockHarborConfig as any, mockApis as any)

expect(result).toBeNull()
})
Expand Down
Loading
Loading