feat(f6.8): harden offline field flow and act documents
Android CI / RC / Android · lint, tests, debug APK, release compile (push) Successful in 3m41s
DH V2 CI / API · typecheck, tests, build (push) Successful in 31s
DH V2 CI / WEB · typecheck, build (push) Successful in 19s
Production dependency audit / API · production dependencies (push) Successful in 9s
Production dependency audit / WEB · production dependencies (push) Successful in 8s
DH V2 CI / Docker / scripts contract (push) Successful in 1m14s
Android CI / RC / Android · lint, tests, debug APK, release compile (push) Successful in 3m41s
DH V2 CI / API · typecheck, tests, build (push) Successful in 31s
DH V2 CI / WEB · typecheck, build (push) Successful in 19s
Production dependency audit / API · production dependencies (push) Successful in 9s
Production dependency audit / WEB · production dependencies (push) Successful in 8s
DH V2 CI / Docker / scripts contract (push) Successful in 1m14s
This commit is contained in:
@@ -34,9 +34,9 @@ test('F4 Android uses only lock and seal endpoints for the active Act lifecycle'
|
||||
assert.doesNotMatch(mobileActs, /suspend fun closeAct\(/);
|
||||
});
|
||||
|
||||
test('F4 Android ViewModel calls lock and seal directly', () => {
|
||||
assert.match(viewModel, /actsRepository\.lock\(actId, urgency\)/);
|
||||
assert.match(viewModel, /actsRepository\.seal\(actId\)/);
|
||||
test('F6.8 Android ViewModel persists lock and seal through the offline queue', () => {
|
||||
assert.match(viewModel, /offlineQueue\.enqueueActLock\(actId, urgency\)/);
|
||||
assert.match(viewModel, /offlineQueue\.enqueueSeal\(actId\)/);
|
||||
assert.doesNotMatch(viewModel, /actsRepository\.prepare\(/);
|
||||
assert.doesNotMatch(viewModel, /actsRepository\.reopen\(/);
|
||||
assert.doesNotMatch(viewModel, /actsRepository\.closeAct\(/);
|
||||
|
||||
@@ -4,9 +4,9 @@ import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { API_PHASE, API_VERSION } from '../../src/version';
|
||||
|
||||
test('health metadata reports the current F6.7 release', () => {
|
||||
assert.equal(API_PHASE, 'F6.7');
|
||||
test('health metadata reports the current F6.8 release', () => {
|
||||
assert.equal(API_PHASE, 'F6.8');
|
||||
const pkg = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json'), 'utf8')) as { version: string };
|
||||
assert.equal(API_VERSION, pkg.version);
|
||||
assert.equal(API_VERSION, '0.29.0-7');
|
||||
assert.equal(API_VERSION, '0.29.0-8');
|
||||
});
|
||||
@@ -63,7 +63,7 @@ test('F5.1 Finding Catalog defaults to associated findings and exposes all items
|
||||
assert.match(panel, /available\.filter\(\(item\) => savedIds\.has\(item\.id\)\)/);
|
||||
assert.match(panel, /Buscar dentro de \{viewMode === 'ASSOCIATED' \? 'los asociados' : 'todo el Catálogo'\}/);
|
||||
assert.match(panel, /Todos para vincular/);
|
||||
assert.match(panel, /Esta clasificación todavía no tiene Hallazgos asociados/);
|
||||
assert.match(panel, /Este tipo todavía no tiene Hallazgos asociados/);
|
||||
});
|
||||
|
||||
test('F7 Inventory Configuration exposes only simple types, containment and fields', () => {
|
||||
@@ -116,7 +116,7 @@ test('F7 canonical detail uses one short everyday profile and keeps advanced adm
|
||||
assert.match(detail, />Fotos</);
|
||||
assert.match(detail, />Cambios</);
|
||||
assert.match(detail, /Empresa operadora/);
|
||||
assert.match(detail, /Tipo técnico/);
|
||||
assert.match(detail, /Tipo de instalación \/ subinstalación/);
|
||||
assert.match(detail, /AssetTechnicalDataPanel/);
|
||||
assert.match(detail, /AssetDossierPanel/);
|
||||
assert.match(detail, /Administración avanzada/);
|
||||
|
||||
@@ -10,8 +10,8 @@ function mountedRepoFile(path: string): string {
|
||||
test('F6.3 Android test cut targets production API and has a distinct installable debug version', () => {
|
||||
const gradle = mountedRepoFile('android-app/app/build.gradle.kts');
|
||||
|
||||
assert.match(gradle, /versionCode = 37/);
|
||||
assert.match(gradle, /versionName = "0\.19\.9"/);
|
||||
assert.match(gradle, /versionCode = 38/);
|
||||
assert.match(gradle, /versionName = "0\.19\.10"/);
|
||||
assert.match(gradle, /https:\/\/dhv2\.korexlabs\.com\/api\/v3\//);
|
||||
assert.match(gradle, /applicationIdSuffix = "\.debug"/);
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ test('F6.1 presentation metadata keeps the visible WEB version aligned with pack
|
||||
const visibleVersion = version.match(/APP_VERSION\s*=\s*'([^']+)'/)?.[1];
|
||||
|
||||
assert.equal(visibleVersion, pkg.version);
|
||||
assert.match(version, /APP_PHASE\s*=\s*'F6\.7 · Cierre y firma por Acta'/);
|
||||
assert.match(version, /APP_PHASE\s*=\s*'F6\.8/);
|
||||
});
|
||||
|
||||
test('F6.1 presentation keeps Relevamientos retired from WEB navigation and routes', () => {
|
||||
@@ -49,7 +49,7 @@ test('F6.1 presentation keeps Android start, Otro and chronological merge flows
|
||||
assert.match(mobileApi, /field-inventory\/\{assetId\}\/merge/);
|
||||
assert.match(overview, /"Iniciar inspección"/);
|
||||
assert.match(overview, /Otro \/ no catalogado/);
|
||||
assert.match(viewModel, /repository\.startVisit\(id\)/);
|
||||
assert.match(viewModel, /enqueueVisitStart\(id\)|repository\.startVisit\(id\)/);
|
||||
assert.match(viewModel, /selectedFieldAsset = repository\.selectFieldAsset\(visitId, result\.canonical\.id\)/);
|
||||
assert.match(viewModel, /la historia de \$\{result\.source\.code\} permanece trazable/);
|
||||
});
|
||||
|
||||
@@ -36,11 +36,11 @@ test('F6.7 Android goes Draft to Para firmar to company manifestation without in
|
||||
assert.match(labels, /"LOCKED" -> "Para firmar"/);
|
||||
assert.match(android, /Cerrar contenido y pasar a firma/);
|
||||
assert.match(android, /Firma del representante de la empresa \(acompañante\)/);
|
||||
assert.match(android, /Aplicar mi firma y cerrar Acta/);
|
||||
assert.match(android, /cerrar Acta/);
|
||||
assert.doesNotMatch(android, /Firmá como inspector\/a/);
|
||||
assert.doesNotMatch(android, /inspectorSigned/);
|
||||
assert.doesNotMatch(android, /Text\(selected\.code/);
|
||||
assert.match(android, /companyOutcome\?\.status == "ABSENT"/);
|
||||
assert.match(android, /attendanceStatus == "ABSENT"/);
|
||||
});
|
||||
test('F6.7 Dashboard exposes the reusable inspector signature clearly', () => {
|
||||
assert.match(profile, /FIRMA DEL INSPECTOR/);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
const api = (path: string) => readFileSync(resolve(process.cwd(), path), 'utf8');
|
||||
const android = (path: string) => readFileSync(resolve(process.cwd(), '..', 'android-app', path), 'utf8');
|
||||
const web = (path: string) => readFileSync(resolve(process.cwd(), '..', 'web-v2', path), 'utf8');
|
||||
|
||||
test('F6.8 persists field mutations locally and retries only when network is available', () => {
|
||||
const queue = android('app/src/main/java/com/korexlabs/dhinspeccion/data/offline/OfflineQueue.kt');
|
||||
assert.match(queue, /@Database\(entities = \[PendingMobileOperation::class, OfflineCacheEntry::class\]/);
|
||||
assert.match(queue, /NetworkType\.CONNECTED/);
|
||||
assert.match(queue, /OneTimeWorkRequestBuilder<OfflineSyncWorker>/);
|
||||
for (const operation of [
|
||||
'VISIT_START', 'VISIT_CLOSE', 'FIELD_ASSET_CREATE', 'FIELD_ASSET_PHOTO',
|
||||
'ACT_CREATE', 'ACT_ASSET_ENSURE', 'FINDING_CREATE', 'FINDING_PHOTO',
|
||||
'ACT_RESPONSIBLE', 'ACT_LOCK', 'COMPANY_OUTCOME', 'COMPANY_SIGNATURE', 'ACT_SEAL',
|
||||
]) assert.match(queue, new RegExp(`"${operation}"`));
|
||||
});
|
||||
|
||||
test('F6.8 isolates offline work and cached inspection data by authenticated user', () => {
|
||||
const queue = android('app/src/main/java/com/korexlabs/dhinspeccion/data/offline/OfflineQueue.kt');
|
||||
const mobile = android('app/src/main/java/com/korexlabs/dhinspeccion/data/DhMobile.kt');
|
||||
const acts = android('app/src/main/java/com/korexlabs/dhinspeccion/data/MobileActs.kt');
|
||||
const findings = android('app/src/main/java/com/korexlabs/dhinspeccion/data/FieldFindingsMobile.kt');
|
||||
assert.match(queue, /val ownerUserId: String/);
|
||||
assert.match(queue, /ownerUserId=:ownerUserId/);
|
||||
assert.match(queue, /SecureSessionStore/);
|
||||
assert.match(mobile, /userCacheKey/);
|
||||
assert.match(acts, /userCacheKey/);
|
||||
assert.match(findings, /userCacheKey/);
|
||||
});
|
||||
|
||||
test('F6.8 gives offline-created objects idempotent client identities', () => {
|
||||
const actDto = api('src/inspection-acts/dto/create-inspection-act.dto.ts');
|
||||
const findingDto = api('src/inspection-findings/dto/create-inspection-finding.dto.ts');
|
||||
const inventoryDto = api('src/inspection-visits/dto/create-field-inventory.dto.ts');
|
||||
const migration = api('src/database/migrations/1790128200000-f6-8-offline-evidence-idempotency.ts');
|
||||
assert.match(actDto, /clientGeneratedId/);
|
||||
assert.match(findingDto, /clientGeneratedId/);
|
||||
assert.match(inventoryDto, /clientGeneratedId/);
|
||||
assert.match(migration, /inspection_finding_evidence/);
|
||||
assert.match(migration, /asset_field_capture_events/);
|
||||
assert.equal((migration.match(/client_operation_id/g) ?? []).length >= 6, true);
|
||||
});
|
||||
|
||||
test('F6.8 reviews photos before queueing and never sends the retired ONLINE upload mode', () => {
|
||||
const photo = android('app/src/main/java/com/korexlabs/dhinspeccion/ui/FieldFindingScreen.kt');
|
||||
const acts = android('app/src/main/java/com/korexlabs/dhinspeccion/data/MobileActs.kt');
|
||||
assert.match(photo, /Revisar fotografía/);
|
||||
assert.match(photo, /Usar esta foto/);
|
||||
assert.match(photo, /Volver a tomar/);
|
||||
assert.match(photo, /Eliminar foto/);
|
||||
assert.doesNotMatch(acts, /uploadMode: String = "ONLINE"/);
|
||||
assert.match(acts, /uploadMode: String = "IMMEDIATE"/);
|
||||
});
|
||||
|
||||
test('F6.8 makes the real Acta PDF and photographic record first-class in Dashboard', () => {
|
||||
const controller = api('src/inspection-reports/inspection-reports.controller.ts');
|
||||
const page = web('src/pages/InspectionActEditorPage.tsx');
|
||||
const media = web('src/features/inspections/InspectionActMediaPanel.tsx');
|
||||
assert.match(controller, /inspection-acts\/:actId\/pdf/);
|
||||
assert.match(api('src/inspection-acts/inspection-acts.controller.ts'), /:id\/field-media/);
|
||||
assert.match(api('src/inspection-acts/inspection-acts.service.ts'), /capture\.visit_id=act\.visit_id/);
|
||||
assert.match(page, /Abrir PDF del Acta/);
|
||||
assert.match(page, /Descargar PDF/);
|
||||
assert.match(media, /Fotos y Hallazgos/);
|
||||
assert.match(media, /Evidencia fotográfica/);
|
||||
assert.match(media, /Fotos tomadas durante esta inspección/);
|
||||
});
|
||||
|
||||
test('F6.8 presents technical families to users as installation and subinstallation types', () => {
|
||||
const config = web('src/pages/AssetTypesPage.tsx');
|
||||
const detail = web('src/pages/SimpleInventoryDetailPage.tsx');
|
||||
const field = api('src/inspection-visits/f3-field-inventory-structure.service.ts');
|
||||
assert.match(config, /Tipos de Instalación/);
|
||||
assert.match(config, /Tipos de Subinstalación/);
|
||||
assert.match(detail, /Tipo de instalación \/ subinstalación/);
|
||||
assert.match(field, /Tipo de instalación\/subinstalación/);
|
||||
assert.doesNotMatch(config, /clasificación técnica/i);
|
||||
assert.doesNotMatch(detail, /clasificación técnica/i);
|
||||
});
|
||||
Reference in New Issue
Block a user