Add timeout to the rollout status (#425)

* Added timeout to the rollout status and tests for it

* Fixed integration test errors

* Fix for blue green integration test

* Probable fix for integration errors

* No jobs run error fixed

* Changed timeout to file level constant

* Added parsing logic for timeout

* Made tests more concise

* implemented timeout validation check in an extracted utils mod

* Changed function name to parseDuration

* Removed timeout parameter from getResource

---------

Co-authored-by: David Gamero <david340804@gmail.com>
Co-authored-by: Suneha Bose <123775811+bosesuneha@users.noreply.github.com>
This commit is contained in:
benjamin
2025-07-09 13:22:21 -04:00
committed by GitHub
parent e207ec429b
commit ac0b58c9a5
28 changed files with 1677 additions and 209 deletions
@@ -19,6 +19,19 @@ import {ExecOutput} from '@actions/exec'
jest.mock('../../types/kubectl')
const kubectl = new Kubectl('')
const TEST_TIMEOUT = '60s'
// Test constants to follow DRY principle
const EXPECTED_GREEN_OBJECTS = [
{name: 'nginx-service-green', kind: 'Service'},
{name: 'nginx-deployment-green', kind: 'Deployment'}
]
const MOCK_EXEC_OUTPUT = {
exitCode: 0,
stderr: '',
stdout: ''
} as ExecOutput
describe('bluegreenhelper functions', () => {
let testObjects
@@ -40,28 +53,56 @@ describe('bluegreenhelper functions', () => {
[].concat(
testObjects.deploymentEntityList,
testObjects.serviceEntityList
)
),
TEST_TIMEOUT
)
expect(value).toHaveLength(2)
expect(value).toContainEqual({
name: 'nginx-service-green',
kind: 'Service'
expect(value).toHaveLength(EXPECTED_GREEN_OBJECTS.length)
EXPECTED_GREEN_OBJECTS.forEach((expectedObject) => {
expect(value).toContainEqual(expectedObject)
})
expect(value).toContainEqual({
name: 'nginx-deployment-green',
kind: 'Deployment'
})
test('handles timeout when deleting objects', async () => {
// Mock deleteObjects to prevent actual execution
const deleteSpy = jest
.spyOn(kubectl, 'delete')
.mockResolvedValue(MOCK_EXEC_OUTPUT)
await bgHelper.deleteObjects(
kubectl,
EXPECTED_GREEN_OBJECTS,
TEST_TIMEOUT
)
// Verify kubectl.delete is called with timeout for each object in deleteList
expect(deleteSpy).toHaveBeenCalledTimes(EXPECTED_GREEN_OBJECTS.length)
EXPECTED_GREEN_OBJECTS.forEach(({name, kind}) => {
expect(deleteSpy).toHaveBeenCalledWith(
[kind, name],
undefined,
TEST_TIMEOUT
)
})
})
test('parses objects correctly from one file (getManifestObjects)', () => {
expect(testObjects.deploymentEntityList[0].kind).toBe('Deployment')
expect(testObjects.serviceEntityList[0].kind).toBe('Service')
expect(testObjects.ingressEntityList[0].kind).toBe('Ingress')
const expectedTypes = [
{
list: testObjects.deploymentEntityList,
kind: 'Deployment',
selectorApp: 'nginx'
},
{list: testObjects.serviceEntityList, kind: 'Service'},
{list: testObjects.ingressEntityList, kind: 'Ingress'}
]
expect(
testObjects.deploymentEntityList[0].spec.selector.matchLabels.app
).toBe('nginx')
expectedTypes.forEach(({list, kind, selectorApp}) => {
expect(list[0].kind).toBe(kind)
if (selectorApp) {
expect(list[0].spec.selector.matchLabels.app).toBe(selectorApp)
}
})
})
test('parses other kinds of objects (getManifestObjects)', () => {
@@ -102,23 +143,24 @@ describe('bluegreenhelper functions', () => {
})
test('correctly makes new blue green object (getNewBlueGreenObject and addBlueGreenLabelsAndAnnotations)', () => {
const modifiedDeployment = getNewBlueGreenObject(
testObjects.deploymentEntityList[0],
GREEN_LABEL_VALUE
)
const testCases = [
{
object: testObjects.deploymentEntityList[0],
expectedName: 'nginx-deployment-green'
},
{
object: testObjects.serviceEntityList[0],
expectedName: 'nginx-service-green'
}
]
expect(modifiedDeployment.metadata.name).toBe('nginx-deployment-green')
expect(modifiedDeployment.metadata.labels['k8s.deploy.color']).toBe(
'green'
)
const modifiedSvc = getNewBlueGreenObject(
testObjects.serviceEntityList[0],
GREEN_LABEL_VALUE
)
expect(modifiedSvc.metadata.name).toBe('nginx-service-green')
expect(modifiedSvc.metadata.labels['k8s.deploy.color']).toBe('green')
testCases.forEach(({object, expectedName}) => {
const modifiedObject = getNewBlueGreenObject(object, GREEN_LABEL_VALUE)
expect(modifiedObject.metadata.name).toBe(expectedName)
expect(modifiedObject.metadata.labels['k8s.deploy.color']).toBe(
'green'
)
})
})
test('correctly fetches k8s objects', async () => {
@@ -140,24 +182,41 @@ describe('bluegreenhelper functions', () => {
})
test('exits when fails to fetch k8s objects', async () => {
const mockExecOutput = {
stdout: 'this should not matter',
exitCode: 0,
stderr: 'this is a fake error'
} as ExecOutput
jest
.spyOn(kubectl, 'getResource')
.mockImplementation(() => Promise.resolve(mockExecOutput))
let fetched = await fetchResource(
kubectl,
'nginx-deployment',
'Deployment'
)
expect(fetched).toBe(null)
const errorTestCases = [
{
description: 'with stderr error',
mockOutput: {
stdout: 'this should not matter',
exitCode: 0,
stderr: 'this is a fake error'
} as ExecOutput,
mockImplementation: () => Promise.resolve
},
{
description: 'with undefined implementation',
mockOutput: null,
mockImplementation: () => undefined
}
]
jest.spyOn(kubectl, 'getResource').mockImplementation()
fetched = await fetchResource(kubectl, 'nginx-deployment', 'Deployment')
expect(fetched).toBe(null)
for (const testCase of errorTestCases) {
const spy = jest.spyOn(kubectl, 'getResource')
if (testCase.mockOutput) {
spy.mockImplementation(() => Promise.resolve(testCase.mockOutput))
} else {
spy.mockImplementation()
}
const fetched = await fetchResource(
kubectl,
'nginx-deployment',
'Deployment'
)
expect(fetched).toBe(null)
spy.mockRestore()
}
})
test('returns null when fetch fails to unset k8s objects', async () => {