fix: 统一订阅更新冲突语义
This commit is contained in:
parent
986615ac32
commit
761c49ed3c
@ -144,23 +144,28 @@ export class QqbotMessageSubscriptionService {
|
|||||||
input: MessageSubscriptionInput,
|
input: MessageSubscriptionInput,
|
||||||
): Promise<MessageSubscriptionView> {
|
): Promise<MessageSubscriptionView> {
|
||||||
const normalized = await this.normalizeInput(input);
|
const normalized = await this.normalizeInput(input);
|
||||||
const saved = await this.subscriptionRepository.manager.transaction(
|
try {
|
||||||
/** Holds both target and prospective active-key rows through the update save. */
|
const saved = await this.subscriptionRepository.manager.transaction(
|
||||||
async (manager) => {
|
/** Holds both target and prospective active-key rows through the update save. */
|
||||||
const repository = manager.getRepository(QqbotMessageSubscription);
|
async (manager) => {
|
||||||
const current = await this.findActiveForWrite(repository, id);
|
const repository = manager.getRepository(QqbotMessageSubscription);
|
||||||
const conflict = await repository.findOne({
|
const current = await this.findActiveForWrite(repository, id);
|
||||||
lock: { mode: 'pessimistic_write' },
|
const conflict = await repository.findOne({
|
||||||
where: { activeKey: normalized.activeKey, isDeleted: false },
|
lock: { mode: 'pessimistic_write' },
|
||||||
});
|
where: { activeKey: normalized.activeKey, isDeleted: false },
|
||||||
if (conflict && conflict.id !== current.id) {
|
});
|
||||||
this.throwNaturalKeyConflict();
|
if (conflict && conflict.id !== current.id) {
|
||||||
}
|
this.throwNaturalKeyConflict();
|
||||||
Object.assign(current, normalized);
|
}
|
||||||
return repository.save(current);
|
Object.assign(current, normalized);
|
||||||
},
|
return repository.save(current);
|
||||||
);
|
},
|
||||||
return this.toView(saved);
|
);
|
||||||
|
return this.toView(saved);
|
||||||
|
} catch (error) {
|
||||||
|
if (this.isDuplicateKeyError(error)) this.throwNaturalKeyConflict();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -389,6 +389,36 @@ describe('QqbotMessageSubscriptionService', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('maps duplicate-key update races to Vben HTTP 409 and propagates other save failures', async () => {
|
||||||
|
const duplicateKeyRace = setup([subscription()]);
|
||||||
|
duplicateKeyRace.subscriptionRepository.save.mockRejectedValueOnce({
|
||||||
|
code: 'ER_DUP_ENTRY',
|
||||||
|
errno: 1062,
|
||||||
|
});
|
||||||
|
await expect(
|
||||||
|
duplicateKeyRace.service.update('100', {
|
||||||
|
enabled: true,
|
||||||
|
name: '更新竞态',
|
||||||
|
sourceConfig: CONFIG,
|
||||||
|
sourceKey: SOURCE_KEY,
|
||||||
|
}),
|
||||||
|
).rejects.toMatchObject({ status: HttpStatus.CONFLICT });
|
||||||
|
|
||||||
|
const infrastructureFailure = new Error('database unavailable');
|
||||||
|
const persistenceFailure = setup([subscription()]);
|
||||||
|
persistenceFailure.subscriptionRepository.save.mockRejectedValueOnce(
|
||||||
|
infrastructureFailure,
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
persistenceFailure.service.update('100', {
|
||||||
|
enabled: true,
|
||||||
|
name: '更新故障',
|
||||||
|
sourceConfig: CONFIG,
|
||||||
|
sourceKey: SOURCE_KEY,
|
||||||
|
}),
|
||||||
|
).rejects.toBe(infrastructureFailure);
|
||||||
|
});
|
||||||
|
|
||||||
it('pages only undeleted records with real filters and returns detached, real-time source status', async () => {
|
it('pages only undeleted records with real filters and returns detached, real-time source status', async () => {
|
||||||
const source = adapter({
|
const source = adapter({
|
||||||
invalidReasonCode: 'ddns_mapping_mismatch',
|
invalidReasonCode: 'ddns_mapping_mismatch',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user