Premiere commit - Omika

This commit is contained in:
2026-10-23 18:46:50 +02:00
commit 8ea76c1b24
2155 changed files with 984301 additions and 0 deletions
+143
View File
@@ -0,0 +1,143 @@
# ============================================================
# Omika Diagnostic Center - Tests NON invasifs des scripts de deploiement
# (Phase 5B2B.3). AUCUN changement systeme : aucune creation de service,
# aucune connexion PostgreSQL, aucun psql execute, aucun fichier serveur.
# ============================================================
# Valide :
# 1. Syntaxe PowerShell 5.1 (0 erreur) des 3 scripts.
# 2. Resolution des chemins par defaut ($PSScriptRoot corrige).
# 3. Detection psql (explicite / PATH / installation standard / absent).
# 4. Garanties install-phase5b2.ps1 : aucun service cree, aucun demarrage.
# ============================================================
$ErrorActionPreference = 'Stop'
$repo = 'D:\Avalonia\Omika'
$deploySrc = Join-Path $repo 'infra\deploy'
$deployPkg = Join-Path $repo 'artifacts\phase5b2\Deploy'
$tests = 0
$fails = 0
function Assert([bool]$condition, [string]$label) {
$script:tests++
if ($condition) {
Write-Host ("PASS {0}" -f $label) -ForegroundColor Green
} else {
$script:fails++
Write-Host ("FAIL {0}" -f $label) -ForegroundColor Red
}
}
Write-Host "=== 1. Syntaxe PowerShell 5.1 (0 erreur) ==="
$scripts = @(
(Join-Path $deploySrc 'Install-DatabaseSchema.ps1'),
(Join-Path $deploySrc 'install-phase5b2.ps1'),
(Join-Path $deploySrc 'Harden-DatabaseRoles.ps1')
)
foreach ($s in $scripts) {
$tokens = $null; $errors = $null
[System.Management.Automation.Language.Parser]::ParseFile($s, [ref]$tokens, [ref]$errors) | Out-Null
Assert (($errors -eq $null) -or ($errors.Count -eq 0)) ("Syntaxe OK : " + (Split-Path $s -Leaf))
}
Write-Host ""
Write-Host "=== 2. Resolution des chemins par defaut (bug PSScriptRoot corrige) ==="
# Miroir EXACT des expressions des scripts (calculees APRES le bloc param).
$packageRoot = Split-Path -Parent $deployPkg # phase5b2 (parent de Deploy)
$databaseDir = Join-Path $packageRoot 'Database'
Assert (Test-Path -LiteralPath $packageRoot) "PackageRoot par defaut resout vers le package existant"
Assert (Test-Path -LiteralPath $databaseDir) "DatabaseDir par defaut resout vers ..\Database existant"
Assert (Test-Path -LiteralPath (Join-Path $packageRoot 'DiagnosticsWorker')) "Package contient DiagnosticsWorker"
Assert (Test-Path -LiteralPath (Join-Path $packageRoot 'DiagnosticsWeb')) "Package contient DiagnosticsWeb"
Write-Host ""
Write-Host "=== 3. Detection psql (resolver) ==="
function Resolve-PsqlPath {
param([string]$ExplicitPath)
if ($ExplicitPath) {
if (-not (Test-Path -LiteralPath $ExplicitPath)) { return $null }
return (Resolve-Path -LiteralPath $ExplicitPath).Path
}
$inPath = Get-Command psql.exe -ErrorAction SilentlyContinue
if ($inPath) { return $inPath.Source }
$candidates = @(Get-ChildItem -Path 'C:\Program Files\PostgreSQL\*\bin\psql.exe' -ErrorAction SilentlyContinue)
if ($candidates.Count -gt 0) {
$best = $candidates | ForEach-Object {
$version = 0
$verDir = $_.Directory.Parent.Name
if ($verDir -match '^\d+$') { $version = [int]$verDir }
[PSCustomObject]@{ Path = $_.FullName; Version = $version }
} | Sort-Object -Property Version -Descending | Select-Object -First 1
return $best.Path
}
return $null
}
# A. chemin explicite existant -> retourne ce chemin (le resolver ne verifie
# que Test-Path, donc tout fichier existant sert ici de jeton de test).
$explicitProbe = Join-Path $deploySrc 'Install-DatabaseSchema.ps1'
Assert ((Resolve-PsqlPath -ExplicitPath $explicitProbe) -eq (Resolve-Path -LiteralPath $explicitProbe).Path) "PsqlPath explicite existant -> utilise tel quel"
# B. chemin explicite inexistant -> le script doit s'arreter avec erreur claire
# (on verifie ici que le resolver retourne null, et plus bas que le script
# contient bien Write-Error + exit 1).
Assert ((Resolve-PsqlPath -ExplicitPath 'C:\inexistant\psql.exe') -eq $null) "PsqlPath explicite inexistant -> non resolvable (arret clair du script)"
# C. installation standard PostgreSQL -> detectee (si presente sur ce poste) ;
# sinon le fallback retourne null (comportement documente).
$std = Get-ChildItem -Path 'C:\Program Files\PostgreSQL\*\bin\psql.exe' -ErrorAction SilentlyContinue
Assert ($std -ne $null) "Installation standard PostgreSQL detectee (ou absente : fallback null attendu)"
Write-Host (" (poste dev : {0} psql.exe trouve(s) sous C:\Program Files\PostgreSQL)" -f @($std).Count)
Write-Host ""
Write-Host "=== 4. Garanties install-phase5b2.ps1 (aucun service, aucun demarrage) ==="
$installContent = Get-Content -LiteralPath (Join-Path $deploySrc 'install-phase5b2.ps1') -Raw
Assert ($installContent -notmatch 'New-Service') "Aucun New-Service (creation differee a Phase 5C)"
Assert ($installContent -notmatch 'Start-Service') "Aucun Start-Service (jamais de demarrage)"
Assert ($installContent -notmatch '-StartupType\s+Automatic') "Aucun StartupType Automatic"
Assert ($installContent -match 'Set-Service\s+-Name\s+\$ServiceName\s+-StartupType\s+Manual') "Service existant force en Manual"
Assert ($installContent -match 'Stop-Service') "Service existant force en Stopped"
Assert ($installContent -match 'A_DETERMINER_EN_PHASE_5C') "Compte de service non fige (A_DETERMINER_EN_PHASE_5C)"
Write-Host ""
Write-Host "=== 5. Migration 003 (SQL statique, aucun PostgreSQL requis) ==="
$sql003 = Get-Content -LiteralPath (Join-Path $repo 'infra\postgresql\003_diagnostics_roles.sql') -Raw
# 0 commande ALTER INDEX (les commentaires ne comptent pas : ancrage en debut de ligne).
$alterIndexCmds = @([regex]::Matches($sql003, '(?m)^\s*ALTER\s+INDEX\b') | ForEach-Object { $_.Value })
Assert ($alterIndexCmds.Count -eq 0) "Aucune commande ALTER INDEX OWNER (0 occurrence)"
Assert ($sql003 -match 'ALTER\s+TABLE\s+diagnostic_event\s+OWNER\s+TO\s+omika_diagnostics_owner') "ALTER TABLE OWNER diagnostic_event"
Assert ($sql003 -match 'ALTER\s+TABLE\s+telemetry_metric_hourly\s+OWNER\s+TO\s+omika_diagnostics_owner') "ALTER TABLE OWNER telemetry_metric_hourly"
Assert ($sql003 -match 'ALTER\s+SEQUENCE\s+diagnostic_event_id_seq\s+OWNER\s+TO\s+omika_diagnostics_owner') "ALTER SEQUENCE OWNER diagnostic_event_id_seq"
Assert ($sql003 -match 'ALTER\s+SEQUENCE\s+telemetry_metric_hourly_id_seq\s+OWNER\s+TO\s+omika_diagnostics_owner') "ALTER SEQUENCE OWNER telemetry_metric_hourly_id_seq"
Assert ($sql003 -notmatch '(?m)^\s*REASSIGN\s+OWNED') "Aucun REASSIGN OWNED"
Assert ($sql003 -notmatch '(?m)^\s*DROP\b') "Aucun DROP"
Assert ($sql003 -notmatch '(?m)^\s*TRUNCATE\b') "Aucun TRUNCATE"
Assert ($sql003 -notmatch '(?m)^\s*DELETE\s+FROM') "Aucun DELETE"
Assert ($sql003 -notmatch "(?i)\bPASSWORD\s+'") "Aucun mot de passe en dur"
Assert ($sql003 -match 'GRANT\s+CONNECT\s+ON\s+DATABASE\s+omika_diagnostics\s+TO\s+omika_diagnostics_worker') "Worker : CONNECT database"
Assert ($sql003 -match 'GRANT\s+USAGE\s+ON\s+SCHEMA\s+public\s+TO\s+omika_diagnostics_worker') "Worker : USAGE schema public"
Assert ($sql003 -match 'GRANT\s+INSERT\s+ON\s+TABLE\s+diagnostic_event\s+TO\s+omika_diagnostics_worker') "Worker : INSERT diagnostic_event"
Assert ($sql003 -match 'GRANT\s+USAGE\s+ON\s+SEQUENCE\s+diagnostic_event_id_seq\s+TO\s+omika_diagnostics_worker') "Worker : USAGE sequence"
Assert ($sql003 -notmatch 'GRANT\s+(SELECT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER)\s+ON\s+TABLE\s+diagnostic_event\s+TO\s+omika_diagnostics_worker') "Worker : aucun SELECT/UPDATE/DELETE/TRUNCATE/REFERENCES/TRIGGER"
Assert ($sql003 -match 'GRANT\s+CONNECT\s+ON\s+DATABASE\s+omika_diagnostics\s+TO\s+[^;]*omika_diagnostics_web') "Web : CONNECT database"
Assert ($sql003 -match 'GRANT\s+USAGE\s+ON\s+SCHEMA\s+public\s+TO\s+[^;]*omika_diagnostics_web') "Web : USAGE schema public"
Assert ($sql003 -match 'GRANT\s+SELECT\s+ON\s+TABLE\s+diagnostic_event\s+TO\s+omika_diagnostics_web') "Web : SELECT diagnostic_event"
Assert ($sql003 -notmatch 'GRANT\s+(INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER)\s+ON\s+TABLE\s+diagnostic_event\s+TO\s+omika_diagnostics_web') "Web : aucun INSERT/UPDATE/DELETE/TRUNCATE/REFERENCES/TRIGGER"
Assert ($sql003 -notmatch 'GRANT\s+\w+\s+ON\s+(TABLE\s+)?telemetry_metric_hourly') "Aucun droit sur telemetry_metric_hourly"
Assert ($sql003 -match 'ALTER\s+ROLE\s+omika_diagnostics_owner\s+.*NOLOGIN') "owner : NOLOGIN"
Assert ($sql003 -match 'ALTER\s+ROLE\s+omika_diagnostics_worker\s+.*LOGIN') "worker : LOGIN"
Assert ($sql003 -match 'ALTER\s+ROLE\s+omika_diagnostics_web\s+.*LOGIN') "web : LOGIN"
Assert ($sql003 -notmatch 'DROP\s+ROLE\s+omika_diagnostics_app') "Ancien role : aucun DROP ROLE"
Assert ($sql003 -notmatch 'ALTER\s+ROLE\s+omika_diagnostics_app\s+NOLOGIN') "Ancien role : aucun NOLOGIN automatique"
Write-Host ""
Write-Host ("=== RESULTAT : {0} tests, {1} echec(s) ===" -f $tests, $fails)
if ($fails -gt 0) { exit 1 } else { exit 0 }
@@ -0,0 +1,175 @@
# ============================================================
# Omika - Tests NON invasifs de Install-OmikaServerFiles.ps1 (Phase 5C.2B)
# ============================================================
# Valide le correctif WhatIf / SHA256 :
# A. Get-Sha256Hex sur fichier connu.
# B. Get-Sha256Hex sous $WhatIfPreference = $true (aucun null).
# C. Package valide + WhatIf -> SHA256 calcule, exit 0, aucune exception.
# D. Hash incorrect + WhatIf -> mismatch detecte, arret avant copie (exit 1).
# E. Aucune mutation filesystem sous -WhatIf (dossiers + copie).
# F. PackageRoot explicite honore.
# G. PackageRoot automatique ($PSScriptRoot parent).
# AUCUN changement systeme : aucune copie reelle, aucun service, aucun admin.
# ============================================================
[CmdletBinding(SupportsShouldProcess = $true)]
param()
$ErrorActionPreference = 'Stop'
$repo = 'D:\Avalonia\Omika'
$scriptPath = Join-Path $repo 'infra\deploy\Install-OmikaServerFiles.ps1'
$pkgRoot = Join-Path $repo 'artifacts\server-full'
$tests = 0
$fails = 0
function Assert([bool]$condition, [string]$label) {
$script:tests++
if ($condition) {
Write-Host ("PASS {0}" -f $label) -ForegroundColor Green
}
else {
$script:fails++
Write-Host ("FAIL {0}" -f $label) -ForegroundColor Red
}
}
# Exécute un bloc dans un process Windows PowerShell 5.1 enfant (exit-safe),
# retourne @{ Output; ExitCode }. On baisse localement $ErrorActionPreference à
# 'Continue' pendant l'appel natif : ainsi la stderr du natif (ErrorRecord) ne
# lève pas, et 2>&1 la capture dans la sortie.
function Invoke-ChildPS51([string]$scriptText) {
$encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($scriptText))
$prevEAP = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
$out = @(& powershell.exe -NoProfile -NonInteractive -EncodedCommand $encoded 2>&1)
$exitCode = $LASTEXITCODE
}
finally {
$ErrorActionPreference = $prevEAP
}
$text = ($out | ForEach-Object { $_.ToString() }) -join "`n"
return @{ Output = $text; ExitCode = $exitCode }
}
Write-Host "=== 1. Syntaxe PowerShell 5.1 (0 erreur) ==="
$tokens = $null; $errors = $null
[System.Management.Automation.Language.Parser]::ParseFile($scriptPath, [ref]$tokens, [ref]$errors) | Out-Null
Assert (($errors -eq $null) -or ($errors.Count -eq 0)) "Syntaxe OK : Install-OmikaServerFiles.ps1"
Write-Host ""
Write-Host "=== 2. Extraction des fonctions (AST) + module temporaire ==="
$content = Get-Content -LiteralPath $scriptPath -Raw
$ast = [System.Management.Automation.Language.Parser]::ParseInput($content, [ref]$tokens, [ref]$errors)
$want = @('Get-Sha256Hex', 'Test-PackageChecksums', 'Deploy-AppFolder')
$module = New-Object System.Text.StringBuilder
foreach ($f in $ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true)) {
if ($want -contains $f.Name) {
[void]$module.AppendLine($f.Extent.Text)
}
}
foreach ($name in $want) {
Assert ($module.ToString() -match ("function\s+" + [regex]::Escape($name) + "\b")) ("Fonction extraite : " + $name)
}
$modulePath = Join-Path $env:TEMP 'omika-isf-functions.ps1'
[System.IO.File]::WriteAllText($modulePath, $module.ToString(), (New-Object System.Text.UTF8Encoding($true)))
. $modulePath
Write-Host ""
Write-Host "=== A/B. Get-Sha256Hex (unitaire) ==="
$knownFile = Join-Path $env:TEMP 'omika-isf-known.txt'
[System.IO.File]::WriteAllText($knownFile, 'abc', (New-Object System.Text.UTF8Encoding($false)))
$expectedHash = 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
Assert ((Get-Sha256Hex -Path $knownFile) -eq $expectedHash) "A. Hash connu 'abc' = ba7816bf... correct"
$prevWhatIf = $WhatIfPreference
$WhatIfPreference = $true
try {
$hashUnderWhatIf = Get-Sha256Hex -Path $knownFile
Assert ($hashUnderWhatIf -eq $expectedHash) "B. Sous WhatIfPreference=true : hash correct (aucun null)"
Assert ($hashUnderWhatIf -match '^[0-9a-f]{64}$') "B. Format hex lowercase 64 caracteres"
}
finally {
$WhatIfPreference = $prevWhatIf
}
Write-Host ""
Write-Host "=== C. Package valide + WhatIf (Test-PackageChecksums) ==="
$childC = @"
`$ErrorActionPreference = 'Stop'
. '$modulePath'
`$WhatIfPreference = `$true
Test-PackageChecksums -packageRoot '$pkgRoot'
"@
$resC = Invoke-ChildPS51 $childC
Assert ($resC.ExitCode -eq 0) "C. Package valide + WhatIf : exit 0"
Assert ($resC.Output -match 'SHA256 OK') "C. Package valide + WhatIf : SHA256 calcule et OK"
Write-Host ""
Write-Host "=== D. Hash incorrect + WhatIf (mismatch detecte, arret) ==="
$corruptDir = Join-Path $env:TEMP 'omika-isf-corrupt'
if (Test-Path -LiteralPath $corruptDir) { Remove-Item -LiteralPath $corruptDir -Recurse -Force }
New-Item -ItemType Directory -Force -Path $corruptDir | Out-Null
[System.IO.File]::WriteAllText((Join-Path $corruptDir 'test.txt'), 'hello', (New-Object System.Text.UTF8Encoding($false)))
[System.IO.File]::WriteAllText((Join-Path $corruptDir 'SHA256SUMS.txt'), '0000000000000000000000000000000000000000000000000000000000000000 test.txt', (New-Object System.Text.UTF8Encoding($false)))
$childD = @"
`$ErrorActionPreference = 'Stop'
. '$modulePath'
`$WhatIfPreference = `$true
Test-PackageChecksums -packageRoot '$corruptDir'
"@
$resD = Invoke-ChildPS51 $childD
Assert ($resD.ExitCode -eq 1) "D. Hash incorrect + WhatIf : exit 1 (arret avant copie)"
Assert ($resD.Output -match 'Checksum SHA256 KO') "D. Hash incorrect + WhatIf : mismatch detecte"
Write-Host ""
Write-Host "=== E. Aucune mutation filesystem sous -WhatIf ==="
$tempRoot = Join-Path $env:TEMP ('omika-isf-mut-' + [guid]::NewGuid().ToString('N'))
# E1 : boucle de creation d'arborescence (replique de la logique top-level).
$WhatIfPreference = $true
$appsRoot = Join-Path $tempRoot 'Apps'
$folders = @(
$appsRoot,
(Join-Path $appsRoot 'OmikaApi'),
(Join-Path $appsRoot 'OmikaWorker'),
(Join-Path $tempRoot 'Data\Api\Diagnostics\Jsonl'),
(Join-Path $tempRoot 'Data\DiagnosticsWorker\Checkpoint'),
(Join-Path $tempRoot 'Logs\Api'),
(Join-Path $tempRoot 'Config'),
(Join-Path $tempRoot 'Backups'),
(Join-Path $tempRoot 'Deploy')
)
foreach ($folder in $folders) {
if ($PSCmdlet.ShouldProcess($folder, 'Créer le dossier')) {
New-Item -ItemType Directory -Force -Path $folder | Out-Null
}
}
# E2 : Deploy-AppFolder (sauvegarde + copie) sous WhatIf.
Deploy-AppFolder -Name 'OmikaApi' `
-Source (Join-Path $pkgRoot 'OmikaApi') `
-Target (Join-Path $appsRoot 'OmikaApi') `
-BackupRoot (Join-Path $tempRoot 'Backups') `
-Preserve @('appsettings.json')
$WhatIfPreference = $prevWhatIf
Assert (-not (Test-Path -LiteralPath $tempRoot)) "E. Aucun dossier/fichier cree sous WhatIf (arborescence + copie)"
Write-Host ""
Write-Host "=== F/G. Resolution PackageRoot ==="
# G : automatique = parent du dossier Deploy (mirroir exact de $PSScriptRoot).
$autoResolved = Split-Path -Parent (Join-Path $pkgRoot 'Deploy')
Assert ($autoResolved -eq $pkgRoot) "G. PackageRoot automatique (parent du dossier Deploy) resout vers le package"
# F : explicite = honore (le script ne resout que si -not $PackageRoot).
Assert ($content -match 'if \(-not \$PackageRoot\)') "F. Garde 'if (-not PackageRoot)' presente (explicite honore)"
Write-Host ""
Write-Host ("=== RESULTAT : {0} tests, {1} echec(s) ===" -f $tests, $fails)
if ($fails -gt 0) { exit 1 } else { exit 0 }
@@ -0,0 +1,361 @@
# ============================================================
# Omika - Tests NON invasifs des scripts d'installation des services
# ============================================================
# AUCUN changement systeme : aucune creation de service, aucune ACL posee,
# aucune connexion PostgreSQL/HFSQL. Seule une reservation BRIEF de ports
# loopback est faite (pour tester le module), immediatement relachee.
#
# Valide :
# 1. Syntaxe PowerShell 5.1 (0 erreur) de TOUS les fichiers de deploiement.
# 2. Import du module commun + presence des fonctions attendues.
# 3. Garanties : aucun demarrage de service, aucune creation de service hors
# du module, aucun port en dur, -WhatIf / -Desinstaller supportes.
# 4. Fiche reelle de chaque service (nom, executable, compte par defaut).
# 5. Comportement reel de la verification de port (3 cas).
# ============================================================
$ErrorActionPreference = 'Stop'
$deploySrc = Split-Path -Parent $PSScriptRoot
$repo = Split-Path -Parent (Split-Path -Parent $deploySrc)
$tests = 0
$fails = 0
function Assert([bool]$condition, [string]$label) {
$script:tests++
if ($condition) {
Write-Host ("PASS {0}" -f $label) -ForegroundColor Green
}
else {
Write-Host ("FAIL {0}" -f $label) -ForegroundColor Red
$script:fails++
}
}
$moduleCommun = Join-Path $deploySrc 'Omika.Deploy.Common.psm1'
# --- 1. Syntaxe PowerShell 5.1 ----------------------------------------------
Write-Host '=== 1. Syntaxe PowerShell 5.1 (0 erreur) ==='
$fichiers = @(
$moduleCommun,
(Join-Path $deploySrc 'Install-OmikaApi.ps1'),
(Join-Path $deploySrc 'Install-OmikaWorker.ps1'),
(Join-Path $deploySrc 'Install-OmikaDiagnosticsApi.ps1'),
(Join-Path $deploySrc 'Install-OmikaDiagnosticsWorker.ps1'),
(Join-Path $deploySrc 'Install-OmikaDiagnosticsWeb.ps1'),
(Join-Path $deploySrc 'Install-OmikaLanguesApi.ps1'),
(Join-Path $deploySrc 'Install-OmikaServices.ps1'),
(Join-Path $deploySrc 'Test-PortDisponible.ps1'),
(Join-Path $deploySrc 'New-OmikaLanguesManifeste.ps1'),
(Join-Path $deploySrc 'Install-OmikaServerFiles.ps1')
)
foreach ($fichier in $fichiers) {
if (-not (Test-Path -LiteralPath $fichier)) {
Assert $false ("Fichier present : " + (Split-Path $fichier -Leaf))
continue
}
$tokens = $null
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile($fichier, [ref]$tokens, [ref]$errors) | Out-Null
$ok = (($null -eq $errors) -or ($errors.Count -eq 0))
Assert $ok ("Syntaxe OK : " + (Split-Path $fichier -Leaf))
if (-not $ok) {
foreach ($e in $errors) { Write-Host (" " + $e.Message) -ForegroundColor DarkRed }
}
}
# --- 2. Module commun --------------------------------------------------------
Write-Host ''
Write-Host '=== 2. Module commun : import + fonctions exportees ==='
Import-Module $moduleCommun -Force
Assert $true 'Import du module sans erreur'
$fonctionsAttendues = @(
'Assert-OmikaAdmin', 'Get-OmikaPaths', 'New-OmikaDirectory',
'Resolve-OmikaAccountName', 'Assert-OmikaServiceAccount',
'Test-OmikaServiceLogonRight', 'Grant-OmikaAcl',
'Get-OmikaServiceState', 'Install-OmikaService', 'Remove-OmikaService',
'Write-OmikaServiceSummary', 'Test-OmikaPortLibre'
)
foreach ($nom in $fonctionsAttendues) {
$existe = $null -ne (Get-Command -Name $nom -ErrorAction SilentlyContinue)
Assert $existe ("Fonction exportee : $nom")
}
# Le controle du code retour d'icacls est la correction cle de l'ancien script.
$contenuModule = Get-Content -LiteralPath $moduleCommun -Raw
Assert ($contenuModule -match 'LASTEXITCODE') 'Grant-OmikaAcl controle le code retour (LASTEXITCODE)'
Assert ($contenuModule -match 'icacls\.exe') 'ACL posees via icacls.exe (appel explicite)'
Assert ($contenuModule -match 'SeServiceLogonRight') 'Droit « Ouvrir une session en tant que service » verifie'
Assert ($contenuModule -notmatch '(?im)^\s*Start-Service\b') 'Le module ne demarre aucun service'
Assert ($contenuModule -notmatch '(?im)^\s*Restart-Service\b') 'Le module ne redemarre aucun service'
Assert ($contenuModule -match 'Resolve-OmikaAccountName') 'Compte normalise en ORDINATEUR\compte (forme non ambigue)'
# --- Non-regression : detection d'existence d'un compte ----------------------
# Le repli ADSI ([ADSI]("WinNT://ORDINATEUR/compte,user")) ne leve AUCUNE erreur
# pour un compte inexistant : il annoncait « Existe = True » a tort. Un faux
# positif est le pire des resultats ici (le script creerait un service avec un
# compte absent). Le repli est desormais net.exe user. Ces deux assertions
# verrouillent la correction.
# On ne cherche [ADSI] que dans le CODE : le module cite legitimement ADSI dans
# le commentaire qui documente la raison de son retrait.
$codeModule = @(Get-Content -LiteralPath $moduleCommun | Where-Object { $_ -notmatch '^\s*#' })
$adsiDansLeCode = @($codeModule | Where-Object { $_ -match '\[ADSI\]' })
Assert ($adsiDansLeCode.Count -eq 0) 'Aucun appel ADSI dans le code (source du faux positif)'
$compteAbsent = Test-OmikaLocalAccount -Account '.\svc_omika_compte_absent_xyz'
Assert ($compteAbsent.Existe -eq $false) 'Compte inexistant detecte ABSENT (aucun faux positif)'
$comptePresent = Test-OmikaLocalAccount -Account (".\" + $env:USERNAME)
Assert ($comptePresent.Existe -eq $true) ("Compte existant detecte PRESENT ({0})" -f $env:USERNAME)
# --- 3. Fiche reelle de chaque service --------------------------------------
Write-Host ''
Write-Host '=== 3. Un fichier par deploiement, fiche conforme ==='
$servicesAttendus = @(
@{ Script = 'Install-OmikaApi.ps1'; Service = 'OmikaApi'; Exe = 'Omika.Api.exe'; Compte = '\.\\svc_omika_api' }
@{ Script = 'Install-OmikaWorker.ps1'; Service = 'OmikaWorker'; Exe = 'Omika.Worker.exe'; Compte = '\.\\svc_omika_worker' }
@{ Script = 'Install-OmikaDiagnosticsApi.ps1'; Service = 'OmikaDiagnosticsApi'; Exe = 'Omika.Diagnostics.Api.exe'; Compte = '\.\\svc_omika_diagapi' }
@{ Script = 'Install-OmikaDiagnosticsWorker.ps1'; Service = 'OmikaDiagnosticsWorker'; Exe = 'Omika.Diagnostics.Worker.exe'; Compte = '\.\\svc_omika_diag' }
@{ Script = 'Install-OmikaDiagnosticsWeb.ps1'; Service = 'OmikaDiagnosticsWeb'; Exe = 'Omika.Diagnostics.Web.exe'; Compte = '\.\\svc_omika_diagweb' }
@{ Script = 'Install-OmikaLanguesApi.ps1'; Service = 'OmikaLanguesApi'; Exe = 'Omika.Langues.Api.exe'; Compte = '\.\\svc_omika_langues' }
)
foreach ($fiche in $servicesAttendus) {
$chemin = Join-Path $deploySrc $fiche.Script
$contenu = Get-Content -LiteralPath $chemin -Raw
Assert ($contenu -match ('\$ServiceName\s*=\s*''' + $fiche.Service + '''')) `
("{0} : nom de service par defaut = {1}" -f $fiche.Script, $fiche.Service)
Assert ($contenu -match [regex]::Escape($fiche.Exe)) `
("{0} : executable attendu {1}" -f $fiche.Script, $fiche.Exe)
# Motif concatene en CHAINES SIMPLES : en chaine double, PowerShell
# n'echappe pas avec '\' -> "\$Account" deviendrait '\' + la valeur de la
# variable $Account (vide), et le motif ne correspondrait jamais.
Assert ($contenu -match ('\$Account\s*=\s*''' + $fiche.Compte + '''')) `
("{0} : compte par defaut = {1}" -f $fiche.Script, $fiche.Compte)
Assert ($contenu -match 'CmdletBinding\(SupportsShouldProcess\s*=\s*\$true\)') `
("{0} : prend en charge -WhatIf" -f $fiche.Script)
Assert ($contenu -match '\$Desinstaller') `
("{0} : prend en charge -Desinstaller (rollback)" -f $fiche.Script)
Assert ($contenu -match 'Assert-OmikaServiceAccount') `
("{0} : valide le compte de service" -f $fiche.Script)
Assert ($contenu -match 'Import-Module') `
("{0} : utilise le module commun" -f $fiche.Script)
Assert ($contenu -notmatch '(?im)^\s*Start-Service\b') `
("{0} : ne demarre aucun service" -f $fiche.Script)
Assert ($contenu -notmatch '(?im)^\s*New-Service\b') `
("{0} : ne cree pas de service directement (passe par Install-OmikaService)" -f $fiche.Script)
Assert ($contenu -notmatch '(?im)^\s*Set-Service\b.*-StartupType\s+Automatic') `
("{0} : ne force pas le demarrage automatique" -f $fiche.Script)
}
# --- 4. Garanties transverses ------------------------------------------------
Write-Host ''
Write-Host '=== 4. Garanties transverses ==='
# Aucun port en dur : le port se decide contre le plan Apache (doc §6bis).
# On ne cherche PAS le simple mot « 5099 » (il apparait legitimement dans les
# exemples d'usage et dans le test de port) mais les deux formes qui
# constitueraient un CHOIX code en dur : une URL ASPNETCORE_URLS et une valeur
# par defaut de parametre.
$portEnDur = @()
foreach ($fichier in (Get-ChildItem -LiteralPath $deploySrc -Filter *.ps1 -Recurse)) {
$contenu = Get-Content -LiteralPath $fichier.FullName -Raw
if ($contenu -match '127\.0\.0\.1:5099') {
$portEnDur += ("{0} (ASPNETCORE_URLS en dur)" -f $fichier.Name)
}
# 0 est autorise : il signifie « aucun port fourni », ce n'est pas un choix.
elseif ($contenu -match '(?im)^\s*\[int\]\s*\$Port\w*\s*=\s*[1-9]\d*') {
$portEnDur += ("{0} (valeur par defaut de -Port)" -f $fichier.Name)
}
}
Assert ($portEnDur.Count -eq 0) ("Aucun port code en dur dans infra\deploy ({0} fichier(s))" -f $portEnDur.Count)
if ($portEnDur.Count -gt 0) { Write-Host (" " + ($portEnDur -join ', ')) -ForegroundColor DarkRed }
# L'orchestrateur appelle bien les 6 scripts dedies.
$orchestrateur = Get-Content -LiteralPath (Join-Path $deploySrc 'Install-OmikaServices.ps1') -Raw
foreach ($fiche in $servicesAttendus) {
Assert ($orchestrateur -match [regex]::Escape($fiche.Script)) `
("Orchestrateur : appelle {0}" -f $fiche.Script)
}
Assert ($orchestrateur -match 'SupportsShouldProcess') 'Orchestrateur : prend en charge -WhatIf'
Assert ($orchestrateur -match 'InclureLangues') 'Orchestrateur : service Langues optionnel (-InclureLangues)'
Assert ($orchestrateur -match 'SansDiagnosticsApi') 'Orchestrateur : API centrale optionnelle (-SansDiagnosticsApi)'
# --- 5. Comportement reel de la verification de port ------------------------
Write-Host ''
Write-Host '=== 5. Verification de port : 3 cas reels ==='
# Normalisation du compte (forme '.\' -> 'ORDINATEUR\compte').
$normalise = Resolve-OmikaAccountName -Account '.\svc_omika_api'
Assert ($normalise -eq ('{0}\svc_omika_api' -f $env:COMPUTERNAME)) "Compte normalise : $normalise"
# A. Port libre.
$verdictLibre = Test-OmikaPortLibre -Port 5099 -VerifierPlagesReservees:$false
Assert ($verdictLibre.Libre) ("Port 5099 detecte DISPONIBLE" + $(if ($verdictLibre.Libre) { '' } else { " (raison : $($verdictLibre.Raisons))" }))
# B. Port reellement occupe.
$verrou = $null
$verdictOccupe = $null
try {
$verrou = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 5123)
$verrou.Start()
$verdictOccupe = Test-OmikaPortLibre -Port 5123 -VerifierPlagesReservees:$false
}
finally {
if ($verrou) { try { $verrou.Stop() } catch { } }
}
Assert (($null -ne $verdictOccupe) -and (-not $verdictOccupe.Libre)) 'Port 5123 occupe detecte INDISPONIBLE'
# C. Port dans la plage ephemere.
$verdictEphemere = Test-OmikaPortLibre -Port 50000 -VerifierPlagesReservees:$false
Assert (-not $verdictEphemere.Libre) 'Port 50000 (plage ephemere) detecte INDISPONIBLE'
Assert ($verdictEphemere.Raisons -match 'EPHEMERE') 'La raison invoquee mentionne la plage ephemere'
# --- 6. Manifeste des packs de langue : generation reelle --------------------
# Le manifeste contient deux champs MESURES (tailleOctets, sha256) : une valeur
# inventee ne se voit que sur le telephone, ou le pack est refuse. On verifie
# donc la generation contre une source independante (Get-FileHash / FileInfo).
Write-Host ''
Write-Host '=== 6. Manifeste des packs de langue : generation reelle ==='
$generateur = Join-Path $deploySrc 'New-OmikaLanguesManifeste.ps1'
Assert (Test-Path -LiteralPath $generateur) 'Generateur de manifeste present'
$bac = Join-Path ([System.IO.Path]::GetTempPath()) ('omika-manifeste-' + [guid]::NewGuid().ToString('N').Substring(0, 8))
try {
$null = New-Item -ItemType Directory -Path (Join-Path $bac 'es\versFrancais') -Force
$null = New-Item -ItemType Directory -Path (Join-Path $bac 'es\depuisFrancais') -Force
[System.IO.File]::WriteAllBytes((Join-Path $bac 'es\versFrancais\encoder.onnx'), (New-Object byte[] 4096))
[System.IO.File]::WriteAllBytes((Join-Path $bac 'es\versFrancais\vocab.spm'), (New-Object byte[] 256))
[System.IO.File]::WriteAllBytes((Join-Path $bac 'es\depuisFrancais\encoder.onnx'), (New-Object byte[] 4096))
# Fichier inutile volontairement livre : exclu du chargement par l'application,
# mais telecharge par chaque telephone s'il est declare. Doit etre signale.
[System.IO.File]::WriteAllBytes((Join-Path $bac 'es\versFrancais\decoder_with_past_model.onnx'), (New-Object byte[] 512))
$sortieGeneration = (& $generateur -RootPathLangues $bac 6>&1 | Out-String)
$manifeste = Join-Path $bac 'manifest.json'
Assert (($LASTEXITCODE -eq 0) -and (Test-Path -LiteralPath $manifeste)) 'Generation : manifeste ecrit (code 0)'
Assert ($sortieGeneration -match 'past_key_values') 'Generation : decodeur « _with_past » signale (poids mobile inutile)'
$json = Get-Content -LiteralPath $manifeste -Raw -Encoding UTF8 | ConvertFrom-Json
Assert (@($json.packs).Count -eq 1) 'Manifeste : 1 pack'
Assert (@($json.packs[0].fichiers).Count -eq 4) 'Manifeste : 4 fichiers declares'
$ecarts = 0
foreach ($pack in $json.packs) {
foreach ($fichier in $pack.fichiers) {
$chemin = Join-Path (Join-Path (Join-Path $bac $pack.code) $fichier.sens) $fichier.nomFichier
if ((Get-FileHash -LiteralPath $chemin -Algorithm SHA256).Hash.ToLowerInvariant() -ne $fichier.sha256) { $ecarts++ }
if ((Get-Item -LiteralPath $chemin).Length -ne $fichier.tailleOctets) { $ecarts++ }
}
}
Assert ($ecarts -eq 0) 'Manifeste : sha256 et tailles conformes (source independante)'
$octetsManifeste = [System.IO.File]::ReadAllBytes($manifeste)
Assert (-not ($octetsManifeste[0] -eq 0xEF -and $octetsManifeste[1] -eq 0xBB -and $octetsManifeste[2] -eq 0xBF)) `
'Manifeste : UTF-8 sans BOM (fichier de donnees)'
$null = & $generateur -RootPathLangues $bac -Verifier 6>&1
Assert ($LASTEXITCODE -eq 0) 'Verification : manifeste conforme (code 0)'
# Un modele remplace APRES generation doit etre detecte : c'est le controle
# qui evite un pack silencieusement refuse sur le telephone.
[System.IO.File]::WriteAllBytes((Join-Path $bac 'es\versFrancais\vocab.spm'), (New-Object byte[] 300))
$sortieManifeste = (& $generateur -RootPathLangues $bac -Verifier 6>&1 | Out-String)
Assert ($LASTEXITCODE -eq 1) 'Verification : modele modifie detecte (code 1)'
Assert ($sortieManifeste -match 'empreinte differente') 'Verification : ecart d''empreinte annonce'
# Le mobile compare le sens de facon sensible a la casse : un sens a la
# mauvaise casse ferait refuser le pack sans aucune erreur cote serveur.
$casse = Join-Path $bac 'manifest-casse.json'
(Get-Content -LiteralPath $manifeste -Raw -Encoding UTF8).Replace('"sens": "versFrancais"', '"sens": "VersFrancais"') |
Set-Content -LiteralPath $casse -Encoding UTF8
$null = & $generateur -RootPathLangues $bac -Sortie $casse -Verifier 6>&1
Assert ($LASTEXITCODE -eq 1) 'Verification : sens a la mauvaise casse detecte (refus mobile)'
Remove-Item -LiteralPath $casse -Force -ErrorAction SilentlyContinue
# --- Cohabitation avec le script d'export ---------------------------------
# Le dossier des packs peut contenir le cache HuggingFace du script d'export
# (.hf-cache, des milliers de fichiers) et la sauvegarde du manifeste : ni
# l'un ni l'autre n'est un modele, et les signaler noierait le vrai controle
# sous des faux positifs.
$null = New-Item -ItemType Directory -Path (Join-Path $bac '.hf-cache\hub\models--Helsinki-NLP--opus-mt-es-fr') -Force
[System.IO.File]::WriteAllBytes((Join-Path $bac '.hf-cache\hub\models--Helsinki-NLP--opus-mt-es-fr\blob-pytorch'), (New-Object byte[] 128))
# Deuxieme ecriture : le generateur conserve manifest.json.bak
$null = & $generateur -RootPathLangues $bac 6>&1
Assert (Test-Path -LiteralPath (Join-Path $bac 'manifest.json.bak')) 'Regeneration : sauvegarde manifest.json.bak ecrite'
$sortieTechnique = (& $generateur -RootPathLangues $bac -Verifier 6>&1 | Out-String)
Assert ($LASTEXITCODE -eq 0) 'Cohabitation : verification conforme malgre .hf-cache et .bak'
Assert ($sortieTechnique -notmatch 'hf-cache') 'Cohabitation : .hf-cache jamais signale comme fichier non declare'
Assert ($sortieTechnique -notmatch 'manifest\.json\.bak') 'Cohabitation : la sauvegarde .bak n''est pas un faux positif'
}
finally {
if (Test-Path -LiteralPath $bac) {
Remove-Item -LiteralPath $bac -Recurse -Force -ErrorAction SilentlyContinue
}
}
# --- 6bis. Manifeste : poids partages d'un modele familial (commun/) ---------
# Un modele familial range ses ~1,15 Go dans <code>/commun/ et ne laisse qu'un
# modele.json par sens. Si le generateur traitait « commun » comme un sens
# inconnu, il l'ignorerait : le telephone ne pourrait JAMAIS telecharger les
# poids, et le pack serait declare mais inutilisable.
Write-Host ''
Write-Host '=== 6bis. Manifeste : poids partages (commun/) ==='
$bacFamille = Join-Path ([System.IO.Path]::GetTempPath()) ('omika-manifeste-famille-' + [guid]::NewGuid().ToString('N').Substring(0, 8))
try {
$null = New-Item -ItemType Directory -Path (Join-Path $bacFamille 'pt\commun') -Force
$null = New-Item -ItemType Directory -Path (Join-Path $bacFamille 'pt\versFrancais') -Force
$null = New-Item -ItemType Directory -Path (Join-Path $bacFamille 'pt\depuisFrancais') -Force
[System.IO.File]::WriteAllBytes((Join-Path $bacFamille 'pt\commun\encoder_model.onnx'), (New-Object byte[] 2048))
[System.IO.File]::WriteAllBytes((Join-Path $bacFamille 'pt\commun\source.spm'), (New-Object byte[] 128))
[System.IO.File]::WriteAllText((Join-Path $bacFamille 'pt\versFrancais\modele.json'), '{ "version": 1, "jetonCible": ">>fra<<" }')
[System.IO.File]::WriteAllText((Join-Path $bacFamille 'pt\depuisFrancais\modele.json'), '{ "version": 1, "jetonCible": ">>por<<" }')
$sortieFamille = (& $generateur -RootPathLangues $bacFamille 6>&1 | Out-String)
$manifesteFamille = Join-Path $bacFamille 'manifest.json'
Assert (($LASTEXITCODE -eq 0) -and (Test-Path -LiteralPath $manifesteFamille)) 'Manifeste familial : ecrit (code 0)'
Assert ($sortieFamille -notmatch 'sens inconnu') 'Manifeste familial : commun/ n''est pas traite comme un sens inconnu'
$jsonFamille = Get-Content -LiteralPath $manifesteFamille -Raw -Encoding UTF8 | ConvertFrom-Json
$sensFamille = @($jsonFamille.packs[0].fichiers | ForEach-Object { $_.sens } | Sort-Object -Unique)
Assert ($sensFamille -contains 'commun') 'Manifeste familial : le dossier commun est declare'
Assert ($sensFamille -contains 'versFrancais' -and $sensFamille -contains 'depuisFrancais') `
'Manifeste familial : les deux sens (modele.json) sont declares'
Assert ((@($jsonFamille.packs[0].fichiers | Where-Object { $_.sens -eq 'commun' })).Count -eq 2) `
'Manifeste familial : les poids sont declares une seule fois, sous commun'
$null = & $generateur -RootPathLangues $bacFamille -Verifier 6>&1
Assert ($LASTEXITCODE -eq 0) 'Manifeste familial : verification conforme (code 0)'
}
finally {
if (Test-Path -LiteralPath $bacFamille) {
Remove-Item -LiteralPath $bacFamille -Recurse -Force -ErrorAction SilentlyContinue
}
}
# --- Resultat ----------------------------------------------------------------
Write-Host ''
Write-Host ("=== RESULTAT : {0} tests, {1} echec(s) ===" -f $tests, $fails)
if ($fails -gt 0) { exit 1 } else { exit 0 }