mirror of
https://github.com/Azure/k8s-deploy.git
synced 2026-07-01 01:39:26 +08:00
Added error check for canary promote actions (#432)
* Added checkForErrors so canary promote action fails when there is an error * Added tests for checkForErrors * Probable integration error fix * Probable integration error fix * Revert changes back * Added checkForErrors unit tests * Fixed multiple tests issue --------- Co-authored-by: Suneha Bose <123775811+bosesuneha@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
deployWithLabel,
|
||||
deleteGreenObjects,
|
||||
deployObjects,
|
||||
fetchResource,
|
||||
getDeploymentMatchLabels,
|
||||
getManifestObjects,
|
||||
@@ -134,12 +135,20 @@ describe('bluegreenhelper functions', () => {
|
||||
})
|
||||
|
||||
test('correctly makes labeled workloads', async () => {
|
||||
const kubectlApplySpy = jest.spyOn(kubectl, 'apply').mockResolvedValue({
|
||||
stdout: 'deployment.apps/nginx-deployment created',
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
})
|
||||
|
||||
const cwlResult: BlueGreenDeployment = await deployWithLabel(
|
||||
kubectl,
|
||||
testObjects.deploymentEntityList,
|
||||
GREEN_LABEL_VALUE
|
||||
)
|
||||
expect(cwlResult.deployResult.manifestFiles[0]).toBe('')
|
||||
|
||||
kubectlApplySpy.mockRestore()
|
||||
})
|
||||
|
||||
test('correctly makes new blue green object (getNewBlueGreenObject and addBlueGreenLabelsAndAnnotations)', () => {
|
||||
@@ -219,20 +228,23 @@ describe('bluegreenhelper functions', () => {
|
||||
}
|
||||
})
|
||||
|
||||
test('returns null when fetch fails to unset k8s objects', async () => {
|
||||
test('returns undefined when fetch fails to unset k8s objects', async () => {
|
||||
const mockExecOutput = {
|
||||
stdout: 'this should not matter',
|
||||
stdout: JSON.stringify(testObjects.deploymentEntityList[0]),
|
||||
exitCode: 0,
|
||||
stderr: 'this is a fake error'
|
||||
stderr: ''
|
||||
} as ExecOutput
|
||||
|
||||
jest.spyOn(kubectl, 'getResource').mockResolvedValue(mockExecOutput)
|
||||
jest
|
||||
.spyOn(manifestUpdateUtils, 'UnsetClusterSpecificDetails')
|
||||
.mockImplementation(() => {
|
||||
throw new Error('test error')
|
||||
})
|
||||
|
||||
expect(
|
||||
await fetchResource(kubectl, 'nginx-deployment', 'Deployment')
|
||||
).toBe(null)
|
||||
).toBeUndefined()
|
||||
})
|
||||
|
||||
test('gets deployment labels', () => {
|
||||
@@ -252,4 +264,72 @@ describe('bluegreenhelper functions', () => {
|
||||
getDeploymentMatchLabels(testObjects.deploymentEntityList[0])['app']
|
||||
).toBe('nginx')
|
||||
})
|
||||
|
||||
describe('deployObjects', () => {
|
||||
let mockObjects: any[]
|
||||
let kubectlApplySpy: jest.SpyInstance
|
||||
|
||||
const mockSuccessResult: ExecOutput = {
|
||||
stdout: 'deployment.apps/nginx-deployment created',
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
}
|
||||
|
||||
const mockFailureResult: ExecOutput = {
|
||||
stdout: '',
|
||||
stderr: 'error: deployment failed',
|
||||
exitCode: 1
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
// //@ts-ignore
|
||||
// Kubectl.mockClear()
|
||||
mockObjects = [testObjects.deploymentEntityList[0]]
|
||||
kubectlApplySpy = jest.spyOn(kubectl, 'apply')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('should return execution result and manifest files when kubectl apply succeeds', async () => {
|
||||
kubectlApplySpy.mockClear()
|
||||
kubectlApplySpy.mockResolvedValue(mockSuccessResult)
|
||||
|
||||
const result = await deployObjects(kubectl, mockObjects)
|
||||
|
||||
expect(result.execResult).toEqual(mockSuccessResult)
|
||||
const timeoutArg = kubectlApplySpy.mock.calls[0][3]
|
||||
expect(
|
||||
typeof timeoutArg === 'string' || timeoutArg === undefined
|
||||
).toBe(true)
|
||||
|
||||
expect(kubectlApplySpy).toHaveBeenCalledWith(
|
||||
expect.any(Array),
|
||||
expect.any(Boolean),
|
||||
expect.any(Boolean),
|
||||
timeoutArg
|
||||
)
|
||||
expect(kubectlApplySpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should throw an error when kubectl apply fails with non-zero exit code', async () => {
|
||||
kubectlApplySpy.mockClear()
|
||||
kubectlApplySpy.mockResolvedValue(mockFailureResult)
|
||||
|
||||
await expect(deployObjects(kubectl, mockObjects)).rejects.toThrow()
|
||||
const timeoutArg = kubectlApplySpy.mock.calls[0][3]
|
||||
expect(
|
||||
typeof timeoutArg === 'string' || timeoutArg === undefined
|
||||
).toBe(true)
|
||||
|
||||
expect(kubectlApplySpy).toHaveBeenCalledWith(
|
||||
expect.any(Array),
|
||||
expect.any(Boolean),
|
||||
expect.any(Boolean),
|
||||
timeoutArg
|
||||
)
|
||||
expect(kubectlApplySpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -296,5 +296,6 @@ export async function deployObjects(
|
||||
timeout
|
||||
)
|
||||
|
||||
checkForErrors([execResult])
|
||||
return {execResult, manifestFiles}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import {getManifestObjects} from './blueGreenHelper'
|
||||
import {BlueGreenDeployment} from '../../types/blueGreenTypes'
|
||||
import {
|
||||
deployBlueGreen,
|
||||
@@ -12,28 +11,48 @@ import {RouteStrategy} from '../../types/routeStrategy'
|
||||
import * as TSutils from '../../utilities/trafficSplitUtils'
|
||||
import * as bgHelper from './blueGreenHelper'
|
||||
import * as smiHelper from './smiBlueGreenHelper'
|
||||
import {ExecOutput} from '@actions/exec'
|
||||
|
||||
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
|
||||
|
||||
jest.mock('../../types/kubectl')
|
||||
|
||||
// Shared variables and mock objects used across all test suites
|
||||
const mockDeployResult = {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
}
|
||||
|
||||
const mockBgDeployment: BlueGreenDeployment = {
|
||||
deployResult: mockDeployResult,
|
||||
objects: []
|
||||
}
|
||||
|
||||
describe('deploy tests', () => {
|
||||
let testObjects
|
||||
let kubectl: Kubectl
|
||||
let kubectlApplySpy: jest.SpyInstance
|
||||
|
||||
const mockSuccessResult: ExecOutput = {
|
||||
stdout: 'deployment.apps/nginx-deployment created',
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
}
|
||||
|
||||
const mockFailureResult: ExecOutput = {
|
||||
stdout: '',
|
||||
stderr: 'error: deployment failed',
|
||||
exitCode: 1
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
//@ts-ignore
|
||||
Kubectl.mockClear()
|
||||
testObjects = getManifestObjects(ingressFilepath)
|
||||
kubectl = new Kubectl('')
|
||||
kubectlApplySpy = jest.spyOn(kubectl, 'apply')
|
||||
})
|
||||
|
||||
test('correctly determines deploy type and acts accordingly', async () => {
|
||||
const kubectl = new Kubectl('')
|
||||
const mockBgDeployment: BlueGreenDeployment = {
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: []
|
||||
}
|
||||
kubectlApplySpy.mockResolvedValue(mockSuccessResult)
|
||||
|
||||
jest
|
||||
.spyOn(routeTester, 'routeBlueGreenForDeploy')
|
||||
@@ -68,8 +87,9 @@ describe('deploy tests', () => {
|
||||
})
|
||||
|
||||
test('correctly deploys blue/green ingress', async () => {
|
||||
const kc = new Kubectl('')
|
||||
const value = await deployBlueGreenIngress(kc, ingressFilepath)
|
||||
kubectlApplySpy.mockResolvedValue(mockSuccessResult)
|
||||
|
||||
const value = await deployBlueGreenIngress(kubectl, ingressFilepath)
|
||||
const nol = value.objects.map((obj) => {
|
||||
if (obj.kind === 'Service') {
|
||||
expect(obj.metadata.name).toBe('nginx-service-green')
|
||||
@@ -79,39 +99,103 @@ describe('deploy tests', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Consolidated error tests
|
||||
test.each([
|
||||
{
|
||||
name: 'should throw error when kubectl apply fails during blue/green ingress deployment',
|
||||
fn: () => deployBlueGreenIngress(kubectl, ingressFilepath),
|
||||
setup: () => {}
|
||||
},
|
||||
{
|
||||
name: 'should throw error when kubectl apply fails during blue/green deployment with INGRESS strategy',
|
||||
fn: () =>
|
||||
deployBlueGreen(kubectl, ingressFilepath, RouteStrategy.INGRESS),
|
||||
setup: () => {
|
||||
jest
|
||||
.spyOn(routeTester, 'routeBlueGreenForDeploy')
|
||||
.mockImplementation(() => Promise.resolve(mockBgDeployment))
|
||||
jest
|
||||
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
|
||||
.mockImplementation(() => Promise.resolve('v1alpha3'))
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'should throw error when kubectl apply fails during blue/green deployment with SERVICE strategy',
|
||||
fn: () =>
|
||||
deployBlueGreen(kubectl, ingressFilepath, RouteStrategy.SERVICE),
|
||||
setup: () => {
|
||||
jest
|
||||
.spyOn(routeTester, 'routeBlueGreenForDeploy')
|
||||
.mockImplementation(() => Promise.resolve(mockBgDeployment))
|
||||
jest
|
||||
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
|
||||
.mockImplementation(() => Promise.resolve('v1alpha3'))
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'should throw error when kubectl apply fails during blue/green deployment with SMI strategy',
|
||||
fn: () => deployBlueGreen(kubectl, ingressFilepath, RouteStrategy.SMI),
|
||||
setup: () => {
|
||||
jest
|
||||
.spyOn(routeTester, 'routeBlueGreenForDeploy')
|
||||
.mockImplementation(() => Promise.resolve(mockBgDeployment))
|
||||
jest
|
||||
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
|
||||
.mockImplementation(() => Promise.resolve('v1alpha3'))
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'should throw error when deployBlueGreenService fails',
|
||||
fn: () => deployBlueGreenService(kubectl, ingressFilepath),
|
||||
setup: () => {}
|
||||
},
|
||||
{
|
||||
name: 'should throw error when deployBlueGreenSMI fails',
|
||||
fn: () => deployBlueGreenSMI(kubectl, ingressFilepath),
|
||||
setup: () => {}
|
||||
}
|
||||
])('$name', async ({fn, setup}) => {
|
||||
kubectlApplySpy.mockResolvedValue(mockFailureResult)
|
||||
setup()
|
||||
|
||||
await expect(fn()).rejects.toThrow()
|
||||
|
||||
const timeoutArg = kubectlApplySpy.mock.calls[0][3]
|
||||
expect(typeof timeoutArg === 'string' || timeoutArg === undefined).toBe(
|
||||
true
|
||||
)
|
||||
|
||||
expect(kubectlApplySpy).toHaveBeenCalledWith(
|
||||
expect.any(Array),
|
||||
expect.any(Boolean),
|
||||
expect.any(Boolean),
|
||||
timeoutArg
|
||||
)
|
||||
expect(kubectlApplySpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
// Timeout tests
|
||||
describe('deploy timeout tests', () => {
|
||||
let testObjects
|
||||
let kubectl: Kubectl
|
||||
|
||||
beforeEach(() => {
|
||||
//@ts-ignore
|
||||
Kubectl.mockClear()
|
||||
testObjects = getManifestObjects(ingressFilepath)
|
||||
kubectl = new Kubectl('')
|
||||
})
|
||||
|
||||
test('deployBlueGreen with timeout passes to strategy functions', async () => {
|
||||
const kubectl = new Kubectl('')
|
||||
const timeout = '300s'
|
||||
|
||||
const mockBgDeployment: BlueGreenDeployment = {
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: []
|
||||
}
|
||||
|
||||
// Mock the helper functions that are actually called
|
||||
const deployWithLabelSpy = jest
|
||||
.spyOn(bgHelper, 'deployWithLabel')
|
||||
.mockResolvedValue(mockBgDeployment)
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
})
|
||||
.mockResolvedValue(mockDeployResult)
|
||||
const setupSMISpy = jest
|
||||
.spyOn(smiHelper, 'setupSMI')
|
||||
.mockResolvedValue(mockBgDeployment)
|
||||
@@ -171,25 +255,15 @@ describe('deploy timeout tests', () => {
|
||||
})
|
||||
|
||||
test('deployBlueGreenIngress with timeout', async () => {
|
||||
const kubectl = new Kubectl('')
|
||||
const timeout = '240s'
|
||||
|
||||
// Mock the dependencies
|
||||
const deployWithLabelSpy = jest
|
||||
.spyOn(bgHelper, 'deployWithLabel')
|
||||
.mockResolvedValue({
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: []
|
||||
})
|
||||
.mockResolvedValue(mockBgDeployment)
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
})
|
||||
.mockResolvedValue(mockDeployResult)
|
||||
|
||||
await deployBlueGreenIngress(kubectl, ingressFilepath, timeout)
|
||||
|
||||
@@ -213,25 +287,15 @@ describe('deploy timeout tests', () => {
|
||||
})
|
||||
|
||||
test('deployBlueGreenService with timeout', async () => {
|
||||
const kubectl = new Kubectl('')
|
||||
const timeout = '180s'
|
||||
|
||||
// Mock the dependencies
|
||||
const deployWithLabelSpy = jest
|
||||
.spyOn(bgHelper, 'deployWithLabel')
|
||||
.mockResolvedValue({
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: []
|
||||
})
|
||||
.mockResolvedValue(mockBgDeployment)
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
})
|
||||
.mockResolvedValue(mockDeployResult)
|
||||
|
||||
await deployBlueGreenService(kubectl, ingressFilepath, timeout)
|
||||
|
||||
@@ -255,32 +319,18 @@ describe('deploy timeout tests', () => {
|
||||
})
|
||||
|
||||
test('deployBlueGreenSMI with timeout', async () => {
|
||||
const kubectl = new Kubectl('')
|
||||
const timeout = '360s'
|
||||
|
||||
// Mock the dependencies
|
||||
const setupSMISpy = jest.spyOn(smiHelper, 'setupSMI').mockResolvedValue({
|
||||
objects: [],
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
}
|
||||
})
|
||||
const setupSMISpy = jest
|
||||
.spyOn(smiHelper, 'setupSMI')
|
||||
.mockResolvedValue(mockBgDeployment)
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
})
|
||||
.mockResolvedValue(mockDeployResult)
|
||||
const deployWithLabelSpy = jest
|
||||
.spyOn(bgHelper, 'deployWithLabel')
|
||||
.mockResolvedValue({
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: []
|
||||
})
|
||||
.mockResolvedValue(mockBgDeployment)
|
||||
|
||||
await deployBlueGreenSMI(kubectl, ingressFilepath, timeout)
|
||||
|
||||
@@ -304,23 +354,12 @@ describe('deploy timeout tests', () => {
|
||||
})
|
||||
|
||||
test('deploy functions without timeout should pass undefined', async () => {
|
||||
const kubectl = new Kubectl('')
|
||||
|
||||
const deployWithLabelSpy = jest
|
||||
.spyOn(bgHelper, 'deployWithLabel')
|
||||
.mockResolvedValue({
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: []
|
||||
})
|
||||
.mockResolvedValue(mockBgDeployment)
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
})
|
||||
.mockResolvedValue(mockDeployResult)
|
||||
|
||||
await deployBlueGreenIngress(kubectl, ingressFilepath)
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as core from '@actions/core'
|
||||
import {getManifestObjects} from './blueGreenHelper'
|
||||
import {
|
||||
promoteBlueGreenIngress,
|
||||
@@ -11,20 +10,50 @@ import {Kubectl} from '../../types/kubectl'
|
||||
import {MAX_VAL, MIN_VAL, TRAFFIC_SPLIT_OBJECT} from './smiBlueGreenHelper'
|
||||
import * as smiTester from './smiBlueGreenHelper'
|
||||
import * as bgHelper from './blueGreenHelper'
|
||||
import {ExecOutput} from '@actions/exec'
|
||||
|
||||
let testObjects
|
||||
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
|
||||
|
||||
jest.mock('../../types/kubectl')
|
||||
|
||||
// Shared variables used across all test suites
|
||||
let testObjects: any
|
||||
const kubectl = new Kubectl('')
|
||||
|
||||
// Shared mock objects following DRY principle
|
||||
const mockSuccessResult: ExecOutput = {
|
||||
stdout: 'deployment.apps/nginx-deployment created',
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
}
|
||||
|
||||
const mockFailureResult: ExecOutput = {
|
||||
stdout: '',
|
||||
stderr: 'error: deployment failed',
|
||||
exitCode: 1
|
||||
}
|
||||
|
||||
const mockBgDeployment = {
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: []
|
||||
}
|
||||
|
||||
describe('promote tests', () => {
|
||||
let kubectlApplySpy: jest.SpyInstance
|
||||
|
||||
beforeEach(() => {
|
||||
//@ts-ignore
|
||||
Kubectl.mockClear()
|
||||
testObjects = getManifestObjects(ingressFilepath)
|
||||
kubectlApplySpy = jest.spyOn(kubectl, 'apply')
|
||||
})
|
||||
|
||||
test('promote blue/green ingress', async () => {
|
||||
kubectlApplySpy.mockResolvedValue(mockSuccessResult)
|
||||
|
||||
const mockLabels = new Map<string, string>()
|
||||
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.GREEN_LABEL_VALUE
|
||||
|
||||
@@ -67,6 +96,8 @@ describe('promote tests', () => {
|
||||
})
|
||||
|
||||
test('promote blue/green service', async () => {
|
||||
kubectlApplySpy.mockResolvedValue(mockSuccessResult)
|
||||
|
||||
const mockLabels = new Map<string, string>()
|
||||
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.GREEN_LABEL_VALUE
|
||||
jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
|
||||
@@ -106,6 +137,8 @@ describe('promote tests', () => {
|
||||
})
|
||||
|
||||
test('promote blue/green SMI', async () => {
|
||||
kubectlApplySpy.mockResolvedValue(mockSuccessResult)
|
||||
|
||||
const mockLabels = new Map<string, string>()
|
||||
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.NONE_LABEL_VALUE
|
||||
|
||||
@@ -155,6 +188,92 @@ describe('promote tests', () => {
|
||||
|
||||
expect(promoteBlueGreenSMI(kubectl, testObjects)).rejects.toThrow()
|
||||
})
|
||||
|
||||
// Consolidated error tests
|
||||
test.each([
|
||||
{
|
||||
name: 'should throw error when kubectl apply fails during blue/green ingress promotion',
|
||||
fn: () => promoteBlueGreenIngress(kubectl, testObjects),
|
||||
setup: () => {
|
||||
const mockLabels = new Map<string, string>()
|
||||
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] =
|
||||
bgHelper.GREEN_LABEL_VALUE
|
||||
jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
kind: 'Ingress',
|
||||
spec: {},
|
||||
metadata: {labels: mockLabels, name: 'nginx-ingress-green'}
|
||||
})
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'should throw error when kubectl apply fails during blue/green service promotion',
|
||||
fn: () => promoteBlueGreenService(kubectl, testObjects),
|
||||
setup: () => {
|
||||
const mockLabels = new Map<string, string>()
|
||||
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] =
|
||||
bgHelper.GREEN_LABEL_VALUE
|
||||
jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
kind: 'Service',
|
||||
spec: {selector: mockLabels},
|
||||
metadata: {labels: mockLabels, name: 'nginx-service-green'}
|
||||
})
|
||||
)
|
||||
jest
|
||||
.spyOn(servicesTester, 'validateServicesState')
|
||||
.mockResolvedValue(true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'should throw error when kubectl apply fails during blue/green SMI promotion',
|
||||
fn: () => promoteBlueGreenSMI(kubectl, testObjects),
|
||||
setup: () => {
|
||||
const mockTsObject: TrafficSplitObject = {
|
||||
apiVersion: 'v1alpha3',
|
||||
kind: TRAFFIC_SPLIT_OBJECT,
|
||||
metadata: {
|
||||
name: 'nginx-service-trafficsplit',
|
||||
labels: new Map<string, string>(),
|
||||
annotations: new Map<string, string>()
|
||||
},
|
||||
spec: {
|
||||
service: 'nginx-service',
|
||||
backends: [
|
||||
{service: 'nginx-service-stable', weight: MIN_VAL},
|
||||
{service: 'nginx-service-green', weight: MAX_VAL}
|
||||
]
|
||||
}
|
||||
}
|
||||
jest
|
||||
.spyOn(bgHelper, 'fetchResource')
|
||||
.mockResolvedValue(mockTsObject)
|
||||
jest
|
||||
.spyOn(smiTester, 'validateTrafficSplitsState')
|
||||
.mockResolvedValue(true)
|
||||
}
|
||||
}
|
||||
])('$name', async ({fn, setup}) => {
|
||||
kubectlApplySpy.mockClear()
|
||||
kubectlApplySpy.mockResolvedValue(mockFailureResult)
|
||||
setup()
|
||||
|
||||
await expect(fn()).rejects.toThrow()
|
||||
|
||||
const timeoutArg = kubectlApplySpy.mock.calls[0][3]
|
||||
expect(typeof timeoutArg === 'string' || timeoutArg === undefined).toBe(
|
||||
true
|
||||
)
|
||||
|
||||
expect(kubectlApplySpy).toHaveBeenCalledWith(
|
||||
expect.any(Array),
|
||||
expect.any(Boolean),
|
||||
expect.any(Boolean),
|
||||
timeoutArg
|
||||
)
|
||||
expect(kubectlApplySpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
// Timeout tests
|
||||
@@ -166,13 +285,9 @@ describe('promote timeout tests', () => {
|
||||
})
|
||||
|
||||
const mockDeployWithLabel = () =>
|
||||
jest.spyOn(bgHelper, 'deployWithLabel').mockResolvedValue({
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: []
|
||||
})
|
||||
jest
|
||||
.spyOn(bgHelper, 'deployWithLabel')
|
||||
.mockResolvedValue(mockBgDeployment)
|
||||
|
||||
const setupFetchResource = (
|
||||
kind: string,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {getManifestObjects} from './blueGreenHelper'
|
||||
import {Kubectl} from '../../types/kubectl'
|
||||
import {BlueGreenRejectResult} from '../../types/blueGreenTypes'
|
||||
|
||||
import * as TSutils from '../../utilities/trafficSplitUtils'
|
||||
import {
|
||||
@@ -18,16 +17,57 @@ const TEST_TIMEOUT_LONG = '120s'
|
||||
|
||||
jest.mock('../../types/kubectl')
|
||||
|
||||
// Shared mock objects following DRY principle
|
||||
const mockSuccessResult = {
|
||||
stdout: 'deployment.apps/nginx-deployment created',
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
}
|
||||
|
||||
const mockFailureResult = {
|
||||
stdout: '',
|
||||
stderr: 'error: deployment failed',
|
||||
exitCode: 1
|
||||
}
|
||||
|
||||
const mockBgDeployment = {
|
||||
deployResult: {
|
||||
execResult: {stdout: '', stderr: '', exitCode: 0},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: [
|
||||
{
|
||||
kind: 'Ingress',
|
||||
metadata: {
|
||||
name: 'nginx-ingress',
|
||||
labels: new Map<string, string>()
|
||||
},
|
||||
spec: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const mockDeleteResult = [
|
||||
{name: 'nginx-service-green', kind: 'Service'},
|
||||
{name: 'nginx-deployment-green', kind: 'Deployment'}
|
||||
]
|
||||
|
||||
describe('reject tests', () => {
|
||||
let testObjects
|
||||
let testObjects: any
|
||||
let kubectlApplySpy: jest.SpyInstance
|
||||
|
||||
beforeEach(() => {
|
||||
//@ts-ignore
|
||||
Kubectl.mockClear()
|
||||
jest.restoreAllMocks()
|
||||
testObjects = getManifestObjects(ingressFilepath)
|
||||
kubectlApplySpy = jest.spyOn(kubectl, 'apply')
|
||||
})
|
||||
|
||||
test('reject blue/green ingress', async () => {
|
||||
// Mock kubectl.apply to return successful result
|
||||
kubectlApplySpy.mockResolvedValue(mockSuccessResult)
|
||||
|
||||
const value = await rejectBlueGreenIngress(kubectl, testObjects)
|
||||
|
||||
const bgDeployment = value.routeResult
|
||||
@@ -51,27 +91,11 @@ describe('reject tests', () => {
|
||||
// Mock routeBlueGreenIngressUnchanged and deleteGreenObjects
|
||||
jest
|
||||
.spyOn(routeHelper, 'routeBlueGreenIngressUnchanged')
|
||||
.mockResolvedValue({
|
||||
deployResult: {
|
||||
execResult: {stdout: '', stderr: '', exitCode: 0},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: [
|
||||
{
|
||||
kind: 'Ingress',
|
||||
metadata: {
|
||||
name: 'nginx-ingress',
|
||||
labels: new Map<string, string>()
|
||||
},
|
||||
spec: {}
|
||||
}
|
||||
]
|
||||
})
|
||||
.mockResolvedValue(mockBgDeployment)
|
||||
|
||||
jest.spyOn(bgHelper, 'deleteGreenObjects').mockResolvedValue([
|
||||
{name: 'nginx-service-green', kind: 'Service'},
|
||||
{name: 'nginx-deployment-green', kind: 'Deployment'}
|
||||
])
|
||||
jest
|
||||
.spyOn(bgHelper, 'deleteGreenObjects')
|
||||
.mockResolvedValue(mockDeleteResult)
|
||||
|
||||
const value = await rejectBlueGreenIngress(
|
||||
kubectl,
|
||||
@@ -113,10 +137,10 @@ describe('reject tests', () => {
|
||||
})
|
||||
|
||||
test('reject blue/green service', async () => {
|
||||
jest.spyOn(bgHelper, 'deleteGreenObjects').mockResolvedValue([
|
||||
{name: 'nginx-service-green', kind: 'Service'},
|
||||
{name: 'nginx-deployment-green', kind: 'Deployment'}
|
||||
])
|
||||
kubectlApplySpy.mockResolvedValue(mockSuccessResult)
|
||||
jest
|
||||
.spyOn(bgHelper, 'deleteGreenObjects')
|
||||
.mockResolvedValue(mockDeleteResult)
|
||||
|
||||
const value = await rejectBlueGreenService(
|
||||
kubectl,
|
||||
@@ -186,10 +210,62 @@ describe('reject tests', () => {
|
||||
})
|
||||
|
||||
test('reject blue/green SMI', async () => {
|
||||
// Mock kubectl.apply to return successful result
|
||||
kubectlApplySpy.mockResolvedValue(mockSuccessResult)
|
||||
|
||||
jest
|
||||
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
|
||||
.mockImplementation(() => Promise.resolve('v1alpha3'))
|
||||
const rejectResult = await rejectBlueGreenSMI(kubectl, testObjects)
|
||||
expect(rejectResult.deleteResult).toHaveLength(2)
|
||||
})
|
||||
|
||||
// Consolidated error tests
|
||||
test.each([
|
||||
{
|
||||
name: 'should throw error when kubectl apply fails during blue/green ingress rejection',
|
||||
fn: () => rejectBlueGreenIngress(kubectl, testObjects),
|
||||
setup: () => {
|
||||
jest
|
||||
.spyOn(bgHelper, 'deleteGreenObjects')
|
||||
.mockResolvedValue(mockDeleteResult)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'should throw error when kubectl apply fails during blue/green service rejection',
|
||||
fn: () => rejectBlueGreenService(kubectl, testObjects),
|
||||
setup: () => {
|
||||
jest
|
||||
.spyOn(bgHelper, 'deleteGreenObjects')
|
||||
.mockResolvedValue(mockDeleteResult)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'should throw error when kubectl apply fails during blue/green SMI rejection',
|
||||
fn: () => rejectBlueGreenSMI(kubectl, testObjects),
|
||||
setup: () => {
|
||||
jest
|
||||
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
|
||||
.mockImplementation(() => Promise.resolve('v1alpha3'))
|
||||
}
|
||||
}
|
||||
])('$name', async ({fn, setup}) => {
|
||||
kubectlApplySpy.mockClear()
|
||||
kubectlApplySpy.mockResolvedValue(mockFailureResult)
|
||||
setup()
|
||||
|
||||
await expect(fn()).rejects.toThrow()
|
||||
const timeoutArg = kubectlApplySpy.mock.calls[0][3]
|
||||
expect(typeof timeoutArg === 'string' || timeoutArg === undefined).toBe(
|
||||
true
|
||||
)
|
||||
|
||||
expect(kubectlApplySpy).toHaveBeenCalledWith(
|
||||
expect.any(Array),
|
||||
expect.any(Boolean),
|
||||
expect.any(Boolean),
|
||||
timeoutArg
|
||||
)
|
||||
expect(kubectlApplySpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import * as core from '@actions/core'
|
||||
import {K8sIngress, TrafficSplitObject} from '../../types/k8sObject'
|
||||
import {Kubectl} from '../../types/kubectl'
|
||||
import * as fileHelper from '../../utilities/fileUtils'
|
||||
import * as TSutils from '../../utilities/trafficSplitUtils'
|
||||
import {RouteStrategy} from '../../types/routeStrategy'
|
||||
import {getBufferTime} from '../../inputUtils'
|
||||
import * as inputUtils from '../../inputUtils'
|
||||
import {BlueGreenManifests} from '../../types/blueGreenTypes'
|
||||
|
||||
import {
|
||||
@@ -25,19 +22,36 @@ jest.mock('../../types/kubectl')
|
||||
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
|
||||
const kc = new Kubectl('')
|
||||
|
||||
// Shared mock objects following DRY principle
|
||||
const mockSuccessResult = {
|
||||
stdout: 'deployment.apps/nginx-deployment created',
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
}
|
||||
|
||||
const mockFailureResult = {
|
||||
stdout: '',
|
||||
stderr: 'error: deployment failed',
|
||||
exitCode: 1
|
||||
}
|
||||
|
||||
describe('route function tests', () => {
|
||||
let testObjects: BlueGreenManifests
|
||||
let kubectlApplySpy: jest.SpyInstance
|
||||
|
||||
beforeEach(() => {
|
||||
//@ts-ignore
|
||||
Kubectl.mockClear()
|
||||
|
||||
testObjects = getManifestObjects(ingressFilepath)
|
||||
kubectlApplySpy = jest.spyOn(kc, 'apply')
|
||||
jest
|
||||
.spyOn(fileHelper, 'writeObjectsToFile')
|
||||
.mockImplementationOnce(() => [''])
|
||||
})
|
||||
|
||||
test('correctly prepares blue/green ingresses for deployment', async () => {
|
||||
kubectlApplySpy.mockResolvedValue(mockSuccessResult)
|
||||
|
||||
const unroutedIngCopy: K8sIngress = JSON.parse(
|
||||
JSON.stringify(testObjects.ingressEntityList[0])
|
||||
)
|
||||
@@ -118,21 +132,80 @@ describe('route function tests', () => {
|
||||
(smiResult.objects as TrafficSplitObject[])[0].spec.backends
|
||||
).toHaveLength(2)
|
||||
})
|
||||
|
||||
// Consolidated error tests
|
||||
test.each([
|
||||
{
|
||||
name: 'should throw error when kubectl apply fails during blue/green ingress routing',
|
||||
fn: () =>
|
||||
routeBlueGreenIngress(
|
||||
kc,
|
||||
testObjects.serviceNameMap,
|
||||
testObjects.ingressEntityList
|
||||
),
|
||||
setup: () => {}
|
||||
},
|
||||
{
|
||||
name: 'should throw error when kubectl apply fails during blue/green service routing',
|
||||
fn: () =>
|
||||
routeBlueGreenService(
|
||||
kc,
|
||||
GREEN_LABEL_VALUE,
|
||||
testObjects.serviceEntityList
|
||||
),
|
||||
setup: () => {}
|
||||
},
|
||||
{
|
||||
name: 'should throw error when kubectl apply fails during blue/green SMI routing',
|
||||
fn: () =>
|
||||
routeBlueGreenSMI(
|
||||
kc,
|
||||
GREEN_LABEL_VALUE,
|
||||
testObjects.serviceEntityList
|
||||
),
|
||||
setup: () => {
|
||||
jest
|
||||
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
|
||||
.mockImplementation(() => Promise.resolve('v1alpha3'))
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'should throw error when kubectl apply fails during blue/green ingress unchanged routing',
|
||||
fn: () =>
|
||||
routeBlueGreenIngressUnchanged(
|
||||
kc,
|
||||
testObjects.serviceNameMap,
|
||||
testObjects.ingressEntityList
|
||||
),
|
||||
setup: () => {}
|
||||
}
|
||||
])('$name', async ({fn, setup}) => {
|
||||
kubectlApplySpy.mockClear()
|
||||
kubectlApplySpy.mockResolvedValue(mockFailureResult)
|
||||
setup()
|
||||
|
||||
await expect(fn()).rejects.toThrow()
|
||||
expect(kubectlApplySpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
// Timeout tests
|
||||
describe('route timeout tests', () => {
|
||||
let testObjects: BlueGreenManifests
|
||||
|
||||
beforeEach(() => {
|
||||
//@ts-ignore
|
||||
Kubectl.mockClear()
|
||||
|
||||
testObjects = getManifestObjects(ingressFilepath)
|
||||
jest
|
||||
.spyOn(fileHelper, 'writeObjectsToFile')
|
||||
.mockImplementationOnce(() => [''])
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
test('routeBlueGreenService with timeout', async () => {
|
||||
const timeout = '240s'
|
||||
|
||||
@@ -140,7 +213,7 @@ describe('route timeout tests', () => {
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(require('./blueGreenHelper'), 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
execResult: mockSuccessResult,
|
||||
manifestFiles: []
|
||||
})
|
||||
|
||||
@@ -172,7 +245,7 @@ describe('route timeout tests', () => {
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(require('./blueGreenHelper'), 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
execResult: mockSuccessResult,
|
||||
manifestFiles: []
|
||||
})
|
||||
|
||||
@@ -201,6 +274,7 @@ describe('route timeout tests', () => {
|
||||
expect.any(Array),
|
||||
timeout
|
||||
)
|
||||
expect(value.objects).toHaveLength(1)
|
||||
|
||||
deployObjectsSpy.mockRestore()
|
||||
createTrafficSplitSpy.mockRestore()
|
||||
@@ -213,7 +287,7 @@ describe('route timeout tests', () => {
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(require('./blueGreenHelper'), 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
execResult: mockSuccessResult,
|
||||
manifestFiles: []
|
||||
})
|
||||
|
||||
@@ -229,6 +303,7 @@ describe('route timeout tests', () => {
|
||||
expect.any(Array),
|
||||
timeout
|
||||
)
|
||||
expect(value.objects).toHaveLength(1)
|
||||
|
||||
deployObjectsSpy.mockRestore()
|
||||
})
|
||||
@@ -237,7 +312,7 @@ describe('route timeout tests', () => {
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(require('./blueGreenHelper'), 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
execResult: mockSuccessResult,
|
||||
manifestFiles: []
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as core from '@actions/core'
|
||||
import {TrafficSplitObject} from '../../types/k8sObject'
|
||||
import {Kubectl} from '../../types/kubectl'
|
||||
import * as fileHelper from '../../utilities/fileUtils'
|
||||
@@ -21,7 +20,6 @@ import {
|
||||
MIN_VAL,
|
||||
setupSMI,
|
||||
TRAFFIC_SPLIT_OBJECT,
|
||||
TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX,
|
||||
validateTrafficSplitsState
|
||||
} from './smiBlueGreenHelper'
|
||||
import * as bgHelper from './blueGreenHelper'
|
||||
@@ -30,6 +28,20 @@ jest.mock('../../types/kubectl')
|
||||
|
||||
const kc = new Kubectl('')
|
||||
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
|
||||
|
||||
// Shared mock objects following DRY principle
|
||||
const mockSuccessResult = {
|
||||
stdout: 'service/nginx-service-stable created',
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
}
|
||||
|
||||
const mockFailureResult = {
|
||||
stdout: '',
|
||||
stderr: 'error: service creation failed',
|
||||
exitCode: 1
|
||||
}
|
||||
|
||||
const mockTsObject: TrafficSplitObject = {
|
||||
apiVersion: 'v1alpha3',
|
||||
kind: TRAFFIC_SPLIT_OBJECT,
|
||||
@@ -70,6 +82,8 @@ describe('SMI Helper tests', () => {
|
||||
})
|
||||
|
||||
test('setupSMI tests', async () => {
|
||||
jest.spyOn(kc, 'apply').mockResolvedValue(mockSuccessResult)
|
||||
|
||||
const smiResults = await setupSMI(kc, testObjects.serviceEntityList)
|
||||
|
||||
let found = 0
|
||||
@@ -198,13 +212,29 @@ describe('SMI Helper tests', () => {
|
||||
expect(deleteObjects[0].kind).toBe('Service')
|
||||
})
|
||||
|
||||
// Consolidated error tests using test.each for DRY principle
|
||||
test.each([
|
||||
{
|
||||
name: 'should throw error when kubectl apply fails during SMI setup',
|
||||
fn: () => setupSMI(kc, testObjects.serviceEntityList),
|
||||
setup: () => {
|
||||
jest.spyOn(kc, 'apply').mockResolvedValue(mockFailureResult)
|
||||
}
|
||||
}
|
||||
])('$name', async ({fn, setup}) => {
|
||||
setup()
|
||||
|
||||
await expect(fn()).rejects.toThrow()
|
||||
})
|
||||
|
||||
// Timeout-specific tests
|
||||
test('setupSMI with timeout test', async () => {
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
result: 'success'
|
||||
} as any)
|
||||
execResult: mockSuccessResult,
|
||||
manifestFiles: []
|
||||
})
|
||||
|
||||
const timeout = '300s'
|
||||
const smiResults = await setupSMI(
|
||||
@@ -325,8 +355,9 @@ describe('SMI Helper tests', () => {
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
result: 'success'
|
||||
} as any)
|
||||
execResult: mockSuccessResult,
|
||||
manifestFiles: []
|
||||
})
|
||||
|
||||
const smiResults = await setupSMI(kc, testObjects.serviceEntityList)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user