feat: T6 wave 1 – compliance schema, permissions, lock order, backup
- Migrations (additive): veterinary medicines with lots and movement ledger, treatment medicine/lot/dose links, label particulars, authority notices, apiary restrictions, compliance settings and acknowledgements; notification category COMPLIANCE, added to non-empty Telegram lists. - Permissions veterinary-medicines:* and compliance:* with role grants. - LOCKABLE_TABLES: Apiary locks before Colony; T6 tables appended. - Backup exports and restores the new tables in FK order; unsealed files skip them and report dropped medicine links; an open TSK draft in the target wins. - describeNullContracts shared helper; Telegram backlog e2e no longer depends on the time of day. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
7a1480138d
commit
8a31c42578
23 files changed
+2028
-84
No files matched your search
@@ -1,5 +1,3 @@
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { validate } from 'class-validator';
|
||||
import { UpdateApiaryDto } from '../../apiaries/dto/update-apiary.dto';
|
||||
import { UpdateAutomationRuleDto } from '../../automation/dto/update-automation-rule.dto';
|
||||
import { UpdateBackupSettingsDto } from '../../backup/dto/update-backup-settings.dto';
|
||||
@@ -35,15 +33,10 @@ import { UpdateUserDto } from '../../users/dto/update-user.dto';
|
||||
import { UpdateVarroaMeasurementDto } from '../../varroa/dto/update-varroa-measurement.dto';
|
||||
import { UpdateWebhookDto } from '../../webhooks/dto/update-webhook.dto';
|
||||
import { UpdateWinterCheckDto } from '../../winter-checks/dto/update-winter-check.dto';
|
||||
|
||||
// PATCH contract of every update DTO: an omitted field stays unchanged, null
|
||||
// clears a field whose column is nullable, and null on a column that cannot
|
||||
// hold it is a 400 - never silently ignored, never a 500 from the write.
|
||||
interface NullContract {
|
||||
dto: new () => object;
|
||||
notNull: string[];
|
||||
clearable: string[];
|
||||
}
|
||||
import {
|
||||
describeNullContracts,
|
||||
NullContract,
|
||||
} from './null-contract.spec-helper';
|
||||
|
||||
const contracts: Record<string, NullContract> = {
|
||||
UpdateApiaryDto: {
|
||||
@@ -374,37 +367,4 @@ const contracts: Record<string, NullContract> = {
|
||||
},
|
||||
};
|
||||
|
||||
async function errorPropertiesFor(
|
||||
dto: new () => object,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<string[]> {
|
||||
const errors = await validate(plainToInstance(dto, body), {
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
});
|
||||
return errors.map((error) => error.property);
|
||||
}
|
||||
|
||||
describe('update DTO null handling', () => {
|
||||
describe.each(Object.entries(contracts))('%s', (_name, contract) => {
|
||||
it('accepts an empty patch', async () => {
|
||||
await expect(errorPropertiesFor(contract.dto, {})).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
if (contract.notNull.length > 0) {
|
||||
it.each(contract.notNull)('rejects null for %s', async (field) => {
|
||||
await expect(
|
||||
errorPropertiesFor(contract.dto, { [field]: null }),
|
||||
).resolves.toEqual([field]);
|
||||
});
|
||||
}
|
||||
|
||||
if (contract.clearable.length > 0) {
|
||||
it.each(contract.clearable)('accepts null for %s', async (field) => {
|
||||
await expect(
|
||||
errorPropertiesFor(contract.dto, { [field]: null }),
|
||||
).resolves.toEqual([]);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
describeNullContracts('update DTO null handling', contracts);
|
||||
Reference in new issue
Block a user