fix(scheduler): eliminate read/write concurrency hazzard while updating predicate status
This commit is contained in:
parent
049e66770a
commit
4ab0d24d24
3 changed files with 8 additions and 70 deletions
|
@ -1,8 +1,7 @@
|
||||||
import { PredicateAuthority, PredicateName, PredicateVerificationResult } from '@standardnotes/predicates'
|
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { JobDoneInterpreterInterface } from '../../Job/JobDoneInterpreterInterface'
|
import { PredicateAuthority, PredicateName, PredicateVerificationResult } from '@standardnotes/predicates'
|
||||||
import { JobRepositoryInterface } from '../../Job/JobRepositoryInterface'
|
|
||||||
import { Predicate } from '../../Predicate/Predicate'
|
import { Predicate } from '../../Predicate/Predicate'
|
||||||
import { PredicateRepositoryInterface } from '../../Predicate/PredicateRepositoryInterface'
|
import { PredicateRepositoryInterface } from '../../Predicate/PredicateRepositoryInterface'
|
||||||
import { PredicateStatus } from '../../Predicate/PredicateStatus'
|
import { PredicateStatus } from '../../Predicate/PredicateStatus'
|
||||||
|
@ -11,12 +10,10 @@ import { UpdatePredicateStatus } from './UpdatePredicateStatus'
|
||||||
|
|
||||||
describe('UpdatePredicateStatus', () => {
|
describe('UpdatePredicateStatus', () => {
|
||||||
let predicateRepository: PredicateRepositoryInterface
|
let predicateRepository: PredicateRepositoryInterface
|
||||||
let jobRepository: JobRepositoryInterface
|
|
||||||
let jobDoneInterpreter: JobDoneInterpreterInterface
|
|
||||||
let predicateComplete: Predicate
|
let predicateComplete: Predicate
|
||||||
let predicateIncomplete: Predicate
|
let predicateIncomplete: Predicate
|
||||||
|
|
||||||
const createUseCase = () => new UpdatePredicateStatus(predicateRepository, jobRepository, jobDoneInterpreter)
|
const createUseCase = () => new UpdatePredicateStatus(predicateRepository)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
predicateComplete = {
|
predicateComplete = {
|
||||||
|
@ -33,15 +30,9 @@ describe('UpdatePredicateStatus', () => {
|
||||||
predicateRepository = {} as jest.Mocked<PredicateRepositoryInterface>
|
predicateRepository = {} as jest.Mocked<PredicateRepositoryInterface>
|
||||||
predicateRepository.findByJobUuid = jest.fn().mockReturnValue([predicateComplete, predicateIncomplete])
|
predicateRepository.findByJobUuid = jest.fn().mockReturnValue([predicateComplete, predicateIncomplete])
|
||||||
predicateRepository.save = jest.fn()
|
predicateRepository.save = jest.fn()
|
||||||
|
|
||||||
jobRepository = {} as jest.Mocked<JobRepositoryInterface>
|
|
||||||
jobRepository.markJobAsDone = jest.fn()
|
|
||||||
|
|
||||||
jobDoneInterpreter = {} as jest.Mocked<JobDoneInterpreterInterface>
|
|
||||||
jobDoneInterpreter.interpret = jest.fn()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should mark a predicate as complete and update job as done', async () => {
|
it('should mark a predicate as complete', async () => {
|
||||||
expect(
|
expect(
|
||||||
await createUseCase().execute({
|
await createUseCase().execute({
|
||||||
predicate: { name: PredicateName.EmailBackupsEnabled, jobUuid: '1-2-3', authority: PredicateAuthority.Auth },
|
predicate: { name: PredicateName.EmailBackupsEnabled, jobUuid: '1-2-3', authority: PredicateAuthority.Auth },
|
||||||
|
@ -49,7 +40,6 @@ describe('UpdatePredicateStatus', () => {
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
success: true,
|
success: true,
|
||||||
allPredicatesChecked: true,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(predicateRepository.save).toHaveBeenCalledWith({
|
expect(predicateRepository.save).toHaveBeenCalledWith({
|
||||||
|
@ -57,37 +47,5 @@ describe('UpdatePredicateStatus', () => {
|
||||||
name: 'email-backups-enabled',
|
name: 'email-backups-enabled',
|
||||||
status: 'denied',
|
status: 'denied',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(jobRepository.markJobAsDone).toHaveBeenCalled()
|
|
||||||
expect(jobDoneInterpreter.interpret).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should mark a predicate as complete and not update job as done if there are still incomplete predicates', async () => {
|
|
||||||
predicateRepository.findByJobUuid = jest
|
|
||||||
.fn()
|
|
||||||
.mockReturnValue([
|
|
||||||
predicateComplete,
|
|
||||||
predicateIncomplete,
|
|
||||||
{ uuid: '3-4-5', status: PredicateStatus.Pending } as jest.Mocked<Predicate>,
|
|
||||||
])
|
|
||||||
|
|
||||||
expect(
|
|
||||||
await createUseCase().execute({
|
|
||||||
predicate: { name: PredicateName.EmailBackupsEnabled, jobUuid: '1-2-3', authority: PredicateAuthority.Auth },
|
|
||||||
predicateVerificationResult: PredicateVerificationResult.Denied,
|
|
||||||
}),
|
|
||||||
).toEqual({
|
|
||||||
success: true,
|
|
||||||
allPredicatesChecked: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(predicateRepository.save).toHaveBeenCalledWith({
|
|
||||||
uuid: '2-3-4',
|
|
||||||
name: 'email-backups-enabled',
|
|
||||||
status: 'denied',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(jobRepository.markJobAsDone).not.toHaveBeenCalled()
|
|
||||||
expect(jobDoneInterpreter.interpret).not.toHaveBeenCalled()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
|
||||||
import TYPES from '../../../Bootstrap/Types'
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
import { JobDoneInterpreterInterface } from '../../Job/JobDoneInterpreterInterface'
|
|
||||||
import { JobRepositoryInterface } from '../../Job/JobRepositoryInterface'
|
|
||||||
import { PredicateRepositoryInterface } from '../../Predicate/PredicateRepositoryInterface'
|
import { PredicateRepositoryInterface } from '../../Predicate/PredicateRepositoryInterface'
|
||||||
import { PredicateStatus } from '../../Predicate/PredicateStatus'
|
import { PredicateStatus } from '../../Predicate/PredicateStatus'
|
||||||
import { UseCaseInterface } from '../UseCaseInterface'
|
import { UseCaseInterface } from '../UseCaseInterface'
|
||||||
|
@ -12,35 +10,20 @@ import { UpdatePredicateStatusResponse } from './UpdatePredicateStatusResponse'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class UpdatePredicateStatus implements UseCaseInterface {
|
export class UpdatePredicateStatus implements UseCaseInterface {
|
||||||
constructor(
|
constructor(@inject(TYPES.PredicateRepository) private predicateRepository: PredicateRepositoryInterface) {}
|
||||||
@inject(TYPES.PredicateRepository) private predicateRepository: PredicateRepositoryInterface,
|
|
||||||
@inject(TYPES.JobRepository) private jobRepository: JobRepositoryInterface,
|
|
||||||
@inject(TYPES.JobDoneInterpreter) private jobDoneInterpreter: JobDoneInterpreterInterface,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async execute(dto: UpdatePredicateStatusDTO): Promise<UpdatePredicateStatusResponse> {
|
async execute(dto: UpdatePredicateStatusDTO): Promise<UpdatePredicateStatusResponse> {
|
||||||
const predicates = await this.predicateRepository.findByJobUuid(dto.predicate.jobUuid)
|
const predicates = await this.predicateRepository.findByJobUuid(dto.predicate.jobUuid)
|
||||||
|
|
||||||
let allPredicatesChecked = true
|
|
||||||
for (const predicate of predicates) {
|
for (const predicate of predicates) {
|
||||||
if (predicate.name === dto.predicate.name) {
|
if (predicate.name === dto.predicate.name) {
|
||||||
predicate.status = dto.predicateVerificationResult as unknown as PredicateStatus
|
predicate.status = dto.predicateVerificationResult as unknown as PredicateStatus
|
||||||
await this.predicateRepository.save(predicate)
|
await this.predicateRepository.save(predicate)
|
||||||
}
|
}
|
||||||
if (predicate.status === PredicateStatus.Pending) {
|
|
||||||
allPredicatesChecked = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allPredicatesChecked) {
|
|
||||||
await this.jobDoneInterpreter.interpret(dto.predicate.jobUuid)
|
|
||||||
|
|
||||||
await this.jobRepository.markJobAsDone(dto.predicate.jobUuid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
allPredicatesChecked,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
export type UpdatePredicateStatusResponse =
|
export type UpdatePredicateStatusResponse = {
|
||||||
| {
|
success: boolean
|
||||||
success: true
|
}
|
||||||
allPredicatesChecked: boolean
|
|
||||||
}
|
|
||||||
| { success: false }
|
|
||||||
|
|
Loading…
Reference in a new issue