100 lines
3.7 KiB
JavaScript
100 lines
3.7 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { existsSync, readFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const root = fileURLToPath(new URL('..', import.meta.url));
|
|
const read = (path) => readFileSync(join(root, path), 'utf8');
|
|
|
|
for (const suffix of ['up', 'verify', 'down']) {
|
|
assert.ok(
|
|
existsSync(join(root, `database/migrations/2026081005_m09b_cleaning_rules.${suffix}.sql`)),
|
|
`M09-B ${suffix} migration is missing`
|
|
);
|
|
}
|
|
|
|
const migration = read('database/migrations/2026081005_m09b_cleaning_rules.up.sql');
|
|
for (const pattern of [
|
|
'qipai_cleaning_templates',
|
|
'cleaning_template_version',
|
|
'photo_required',
|
|
'min_photo_count',
|
|
'max_photo_count',
|
|
'exempt_policy',
|
|
'rework_count',
|
|
'qipai_cleaning_task_photos',
|
|
'retention_until',
|
|
'attached_revision',
|
|
'qipai_cleaning_task_submissions',
|
|
'uq_qipai_cleaning_submission_revision'
|
|
]) assert.ok(migration.includes(pattern), `migration is missing ${pattern}`);
|
|
|
|
const repository = read('backend/src/cleaning/cleaning-task-repository.ts');
|
|
for (const pattern of [
|
|
'listTemplates',
|
|
'upsertTemplate',
|
|
"'appliesToExistingTasks', FALSE",
|
|
'listSubmissions',
|
|
'CLEANING_PHOTO_OWNERSHIP_INVALID',
|
|
"status = 'ATTACHED'",
|
|
'attached_revision = ?',
|
|
'claimExpiredPhotoUploads',
|
|
'markPhotoUploadsDeleted',
|
|
'CLEANING_EXEMPT_POLICY_DENIED',
|
|
"ORDER BY FIELD(scope_type, 'ROOM', 'STORE', 'TENANT')"
|
|
]) assert.ok(repository.includes(pattern), `repository is missing ${pattern}`);
|
|
|
|
const routes = read('backend/src/routes/cleaning.ts');
|
|
for (const pattern of [
|
|
'/admin-api/cleaning/templates',
|
|
'/admin-api/cleaning/tasks/:taskId/submissions',
|
|
'/admin-api/cleaning/photos/cleanup',
|
|
'tenantId: actor.tenantId',
|
|
'storeId: context.storeId',
|
|
'deleteImage(image.storagePath)'
|
|
]) assert.ok(routes.includes(pattern), `cleaning routes are missing ${pattern}`);
|
|
|
|
const media = read('backend/src/content/media-storage.ts');
|
|
for (const pattern of [
|
|
'declaredFormat',
|
|
'declaredExtension',
|
|
'metadata.format',
|
|
'.webp({ quality: 82 })',
|
|
'deleteImage'
|
|
]) assert.ok(media.includes(pattern), `media storage is missing ${pattern}`);
|
|
|
|
const automation = read('backend/src/devices/order-device-automation-service.ts');
|
|
for (const pattern of [
|
|
'ROOM_HAS_ACTIVE_ORDER',
|
|
"status IN ('PAID', 'RESERVED', 'IN_PROGRESS')",
|
|
'room_id = ? AND id <> ?'
|
|
]) assert.ok(automation.includes(pattern), `device automation is missing ${pattern}`);
|
|
|
|
const miniapp = read('miniapp/pages/cleaner/tasks.js')
|
|
+ read('miniapp/pages/cleaner/tasks.wxml');
|
|
for (const pattern of [
|
|
'minPhotoCount', 'maxPhotoCount', "task.status === 'STARTED'", 'photoRequired'
|
|
]) assert.ok(miniapp.includes(pattern), `cleaner miniapp is missing ${pattern}`);
|
|
|
|
const admin = read('admin/src/components/CleaningRulesPanel.vue')
|
|
+ read('admin/src/components/CleaningTasksPanel.vue')
|
|
+ read('admin/src/api.ts');
|
|
for (const pattern of [
|
|
'配置变更不会静默改写在途任务',
|
|
'listCleaningTemplates',
|
|
'upsertCleaningTemplate',
|
|
'listCleaningTaskSubmissions',
|
|
'验收照片版本',
|
|
'submission.revision'
|
|
]) assert.ok(admin.includes(pattern), `admin cleaning workflow is missing ${pattern}`);
|
|
|
|
const liveTest = read('backend/tests/mysql-migration-roundtrip.test.mjs');
|
|
for (const pattern of [
|
|
'cleaning template snapshot remains stable after config version change',
|
|
'rejected and accepted cleaning photo revisions remain distinguishable',
|
|
'expired unattached cleaning photo retention cleanup',
|
|
'task-level cleaning exemption policy denial'
|
|
]) assert.ok(liveTest.includes(pattern), `live MySQL test is missing ${pattern}`);
|
|
|
|
console.log('PASS: M09-B cleaning templates, photo security, versioned review and safe device shutdown gates are present.');
|