2021-03-01 18:14:13 +08:00
|
|
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
|
|
import { PipelineTasksService } from './pipeline-tasks.service';
|
|
|
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
|
|
|
import { PipelineTask } from './pipeline-task.entity';
|
2021-03-02 16:28:37 +08:00
|
|
|
import { PIPELINE_TASK_QUEUE } from './pipeline-tasks.constants';
|
|
|
|
import { getQueueToken } from '@nestjs/bull';
|
|
|
|
import { RedisService } from 'nestjs-redis';
|
|
|
|
import { Pipeline } from '../pipelines/pipeline.entity';
|
2021-03-03 17:24:22 +08:00
|
|
|
import { EntityNotFoundError } from 'typeorm/error/EntityNotFoundError';
|
|
|
|
import { Repository } from 'typeorm';
|
|
|
|
import { Queue } from 'bull';
|
|
|
|
import { LockFailedException } from '../commons/exceptions/lock-failed.exception';
|
2021-03-01 18:14:13 +08:00
|
|
|
|
|
|
|
describe('PipelineTasksService', () => {
|
|
|
|
let service: PipelineTasksService;
|
2021-03-03 17:24:22 +08:00
|
|
|
let module: TestingModule;
|
|
|
|
let taskRepository: Repository<PipelineTask>;
|
|
|
|
let pipelineRepository: Repository<Pipeline>;
|
|
|
|
const getBasePipeline = () =>
|
|
|
|
({
|
|
|
|
id: 'test',
|
|
|
|
name: '测试流水线',
|
|
|
|
branch: 'master',
|
2021-03-04 17:02:07 +08:00
|
|
|
workUnitMetadata: {},
|
|
|
|
project: {
|
|
|
|
id: 'test-project',
|
|
|
|
},
|
2021-03-03 17:24:22 +08:00
|
|
|
} as Pipeline);
|
|
|
|
let redisClient;
|
|
|
|
let taskQueue: Queue;
|
|
|
|
const getTask = () =>
|
|
|
|
({
|
|
|
|
pipelineId: 'test',
|
|
|
|
commit: 'test',
|
|
|
|
units: [],
|
|
|
|
} as PipelineTask);
|
2021-03-01 18:14:13 +08:00
|
|
|
|
|
|
|
beforeEach(async () => {
|
2021-03-03 17:24:22 +08:00
|
|
|
redisClient = (() => ({
|
|
|
|
set: jest.fn().mockImplementation(async () => 'OK'),
|
2021-03-05 18:12:34 +08:00
|
|
|
del: jest.fn().mockImplementation(async () => 'test'),
|
|
|
|
get: jest.fn().mockImplementation(async () => 'test'),
|
2021-03-03 17:24:22 +08:00
|
|
|
lpush: jest.fn().mockImplementation(async () => 1),
|
|
|
|
rpop: jest.fn().mockImplementation(async () => JSON.stringify(getTask())),
|
|
|
|
}))() as any;
|
|
|
|
taskQueue = (() => ({
|
|
|
|
add: jest.fn().mockImplementation(async () => null),
|
|
|
|
}))() as any;
|
|
|
|
module = await Test.createTestingModule({
|
2021-03-01 18:14:13 +08:00
|
|
|
providers: [
|
|
|
|
PipelineTasksService,
|
|
|
|
{
|
|
|
|
provide: getRepositoryToken(PipelineTask),
|
2021-03-03 17:24:22 +08:00
|
|
|
useValue: new Repository(),
|
2021-03-01 18:14:13 +08:00
|
|
|
},
|
2021-03-02 16:28:37 +08:00
|
|
|
{
|
|
|
|
provide: getRepositoryToken(Pipeline),
|
2021-03-03 17:24:22 +08:00
|
|
|
useValue: new Repository(),
|
2021-03-02 16:28:37 +08:00
|
|
|
},
|
|
|
|
{
|
|
|
|
provide: getQueueToken(PIPELINE_TASK_QUEUE),
|
2021-03-03 17:24:22 +08:00
|
|
|
useValue: taskQueue,
|
2021-03-02 16:28:37 +08:00
|
|
|
},
|
|
|
|
{
|
|
|
|
provide: RedisService,
|
2021-03-03 17:24:22 +08:00
|
|
|
useValue: {
|
|
|
|
getClient: jest.fn(() => redisClient),
|
|
|
|
},
|
2021-03-02 16:28:37 +08:00
|
|
|
},
|
2021-03-01 18:14:13 +08:00
|
|
|
],
|
|
|
|
}).compile();
|
|
|
|
|
|
|
|
service = module.get<PipelineTasksService>(PipelineTasksService);
|
2021-03-03 17:24:22 +08:00
|
|
|
taskRepository = module.get(getRepositoryToken(PipelineTask));
|
|
|
|
pipelineRepository = module.get(getRepositoryToken(Pipeline));
|
|
|
|
jest
|
|
|
|
.spyOn(taskRepository, 'save')
|
|
|
|
.mockImplementation(async (data: any) => data);
|
|
|
|
jest
|
|
|
|
.spyOn(taskRepository, 'create')
|
|
|
|
.mockImplementation((data: any) => data);
|
2021-03-01 18:14:13 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should be defined', () => {
|
|
|
|
expect(service).toBeDefined();
|
|
|
|
});
|
2021-03-03 17:24:22 +08:00
|
|
|
|
|
|
|
describe('addTask', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
jest
|
|
|
|
.spyOn(pipelineRepository, 'findOneOrFail')
|
|
|
|
.mockImplementation(async () => getBasePipeline());
|
|
|
|
});
|
|
|
|
it('pipeline not found', async () => {
|
|
|
|
jest.spyOn(taskRepository, 'findOneOrFail').mockImplementation(() => {
|
|
|
|
throw new EntityNotFoundError(Pipeline, {});
|
|
|
|
});
|
|
|
|
await expect(
|
|
|
|
service.addTask({ pipelineId: 'test', commit: 'test', units: [] }),
|
|
|
|
).rejects;
|
|
|
|
});
|
|
|
|
it('create task on db', async () => {
|
|
|
|
const save = jest
|
|
|
|
.spyOn(taskRepository, 'save')
|
|
|
|
.mockImplementation(async (data: any) => data);
|
2021-03-05 18:12:34 +08:00
|
|
|
jest
|
|
|
|
.spyOn(service, 'doNextTask')
|
|
|
|
.mockImplementation(async () => undefined);
|
2021-03-03 17:24:22 +08:00
|
|
|
await service.addTask({ pipelineId: 'test', commit: 'test', units: [] }),
|
|
|
|
expect(save.mock.calls[0][0]).toMatchObject({
|
|
|
|
pipelineId: 'test',
|
|
|
|
commit: 'test',
|
|
|
|
units: [],
|
|
|
|
});
|
|
|
|
});
|
|
|
|
it('add task', async () => {
|
|
|
|
const lpush = jest.spyOn(redisClient, 'lpush');
|
|
|
|
const doNextTask = jest.spyOn(service, 'doNextTask');
|
2021-03-05 18:12:34 +08:00
|
|
|
jest
|
|
|
|
.spyOn(service, 'doNextTask')
|
|
|
|
.mockImplementation(async () => undefined);
|
2021-03-03 17:24:22 +08:00
|
|
|
await service.addTask({ pipelineId: 'test', commit: 'test', units: [] });
|
|
|
|
expect(typeof lpush.mock.calls[0][1] === 'string').toBeTruthy();
|
|
|
|
expect(JSON.parse(lpush.mock.calls[0][1] as string)).toMatchObject({
|
|
|
|
pipelineId: 'test',
|
|
|
|
commit: 'test',
|
|
|
|
units: [],
|
|
|
|
pipeline: getBasePipeline(),
|
|
|
|
});
|
|
|
|
expect(doNextTask).toHaveBeenCalledWith(getBasePipeline());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('doNextTask', () => {
|
|
|
|
it('add task to queue', async () => {
|
2021-03-05 18:12:34 +08:00
|
|
|
let lckValue: string;
|
|
|
|
const set = jest
|
|
|
|
.spyOn(redisClient, 'set')
|
|
|
|
.mockImplementation(async (...args) => (lckValue = args[3] as string));
|
|
|
|
const get = jest
|
|
|
|
.spyOn(redisClient, 'get')
|
|
|
|
.mockImplementation(async () => lckValue);
|
|
|
|
const del = jest.spyOn(redisClient, 'del');
|
2021-03-03 17:24:22 +08:00
|
|
|
const rpop = jest.spyOn(redisClient, 'rpop');
|
|
|
|
const add = jest.spyOn(taskQueue, 'add');
|
|
|
|
|
|
|
|
await service.doNextTask(getBasePipeline());
|
|
|
|
|
|
|
|
expect(add).toHaveBeenCalledWith(getTask());
|
2021-03-05 18:12:34 +08:00
|
|
|
expect(set).toHaveBeenCalledTimes(1);
|
2021-03-03 17:24:22 +08:00
|
|
|
expect(rpop).toHaveBeenCalledTimes(1);
|
2021-03-05 18:12:34 +08:00
|
|
|
expect(get).toHaveBeenCalledTimes(1);
|
|
|
|
expect(del).toHaveBeenCalledTimes(1);
|
2021-03-03 17:24:22 +08:00
|
|
|
});
|
|
|
|
it('pipeline is busy', async () => {
|
|
|
|
let remainTimes = 3;
|
|
|
|
|
|
|
|
const incr = jest
|
|
|
|
.spyOn(redisClient, 'incr')
|
|
|
|
.mockImplementation(() => remainTimes--);
|
|
|
|
const rpop = jest.spyOn(redisClient, 'rpop');
|
|
|
|
const decr = jest.spyOn(redisClient, 'decr');
|
|
|
|
const add = jest.spyOn(taskQueue, 'add');
|
|
|
|
|
|
|
|
await service.doNextTask(getBasePipeline());
|
|
|
|
|
|
|
|
expect(rpop).toHaveBeenCalledTimes(1);
|
|
|
|
expect(incr).toHaveBeenCalledTimes(3);
|
|
|
|
expect(decr).toHaveBeenCalledTimes(3);
|
|
|
|
expect(add).toHaveBeenCalledWith(getTask());
|
|
|
|
});
|
|
|
|
it('pipeline always busy and timeout', async () => {
|
2021-03-05 18:12:34 +08:00
|
|
|
const set = jest
|
|
|
|
.spyOn(redisClient, 'set')
|
|
|
|
.mockImplementation(async () => {
|
|
|
|
throw new Error();
|
|
|
|
});
|
|
|
|
const get = jest.spyOn(redisClient, 'get');
|
|
|
|
const del = jest.spyOn(redisClient, 'del');
|
2021-03-03 17:24:22 +08:00
|
|
|
|
|
|
|
await expect(
|
|
|
|
service.doNextTask(getBasePipeline()),
|
|
|
|
).rejects.toBeInstanceOf(LockFailedException);
|
|
|
|
|
2021-03-05 18:12:34 +08:00
|
|
|
expect(set).toHaveBeenCalledTimes(5);
|
|
|
|
expect(get).toHaveBeenCalledTimes(0);
|
|
|
|
expect(del).toHaveBeenCalledTimes(0);
|
2021-03-03 17:24:22 +08:00
|
|
|
}, 15_000);
|
|
|
|
});
|
2021-03-01 18:14:13 +08:00
|
|
|
});
|