test: Szenario 6 Bob-Generaltest in run-integration-tests.ps1

- Haupt-Szenario: Login, PUT 10 Items (3 Kat, 2 Lagerorte), PATCH qty,
  PUT 11 Items (Salzcracker), WS inventoryUpdated, WS fullSyncRequired
- T1: PATCH unbekannte ID -> 404
- T2: PUT leer -> 0 Items
- T3: Zwei PUTs -> zweites ueberschreibt
- T4: Bob patcht selbst -> WS-Event
- T5: Bob/Alice getrennte Inventare
- T6: Ungueltiger Token -> 401
- T7: Zwei WS-Sessions -> beide empfangen Events
- T8: PATCH nach Disconnect -> kein Server-Fehler
- Fix: Receive-WsMessages mit return ,\ (PS-Array-Unwrapping)
- Fix: @()-Wrapping fuer Where-Object-Filter (1-Element-Ergebnis)
Ergebnis: 30/30 Tests bestanden
This commit is contained in:
Jens Reinemann 2026-05-17 01:01:29 +02:00
commit 26b50eea36

View file

@ -86,6 +86,7 @@ function Open-WebSocket([string]$token) {
# WICHTIG: CancellationToken darf NICHT in ReceiveAsync verwendet werden
# das abortet die gesamte .NET ClientWebSocket-Verbindung.
# Stattdessen: Task.Wait(timeout) als Deadline-Mechanismus.
# Gibt immer ein Array zurueck (,$ verhindert PS-Unwrapping bei 1 Element).
function Receive-WsMessages([System.Net.WebSockets.ClientWebSocket]$ws, [int]$waitSeconds = 4) {
$messages = @()
$buffer = New-Object byte[] 8192
@ -105,14 +106,14 @@ function Receive-WsMessages([System.Net.WebSockets.ClientWebSocket]$ws, [int]$wa
if ($result.MessageType -eq [System.Net.WebSockets.WebSocketMessageType]::Close) { break }
if ($result.Count -gt 0) {
$text = [System.Text.Encoding]::UTF8.GetString($buffer, 0, $result.Count)
try { $messages += $text | ConvertFrom-Json } catch { }
try { $messages += ($text | ConvertFrom-Json) } catch { }
}
}
catch {
break
}
}
return $messages
return , $messages # Komma verhindert PS-Unwrapping bei 1 Element
}
function Close-WebSocket([System.Net.WebSockets.ClientWebSocket]$ws) {
@ -392,6 +393,371 @@ if ($aliceTokens -and $bobTokens) {
}
}
# ---------------------------------------------------------------------------
# Szenario 6: Bob-Generaltest (10 Items, PATCH, WebSocket-Push)
# ---------------------------------------------------------------------------
Section "Szenario 6: Bob-Generaltest"
if ($bobTokens) {
# Artikel-IDs fuer das Haupt-Szenario
$dosenBrotId = [System.Guid]::NewGuid().ToString()
$wasserId = [System.Guid]::NewGuid().ToString()
$thunfischId = [System.Guid]::NewGuid().ToString()
$nudelnId = [System.Guid]::NewGuid().ToString()
$toilettenPapierId = [System.Guid]::NewGuid().ToString()
$seifenId = [System.Guid]::NewGuid().ToString()
$muesliId = [System.Guid]::NewGuid().ToString()
$apfelsaftId = [System.Guid]::NewGuid().ToString()
$kerzenId = [System.Guid]::NewGuid().ToString()
$pflasterId = [System.Guid]::NewGuid().ToString()
$bob10Items = @{
version = 1
categories = @(
@{ id = 10; name = "Lebensmittel" }
@{ id = 11; name = "Getraenke" }
@{ id = 12; name = "Hygiene" }
)
locations = @(
@{ id = 10; name = "Keller" }
@{ id = 11; name = "Badezimmer" }
)
items = @(
@{ id = $dosenBrotId; name = "Dosenbrot"; categoryId = 10; quantity = 5.0; unit = "Stueck"; unitPrice = 2.0; kcalPerKg = $null; expiryDate = "2028-12-31"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
@{ id = $wasserId; name = "Mineralwasser"; categoryId = 11; quantity = 24.0; unit = "Stueck"; unitPrice = 0.5; kcalPerKg = $null; expiryDate = "2028-12-31"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
@{ id = $thunfischId; name = "Thunfisch"; categoryId = 10; quantity = 8.0; unit = "Dose"; unitPrice = 1.2; kcalPerKg = $null; expiryDate = "2028-06-30"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
@{ id = $nudelnId; name = "Nudeln"; categoryId = 10; quantity = 3.0; unit = "Packung"; unitPrice = 0.9; kcalPerKg = $null; expiryDate = "2029-01-01"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
@{ id = $toilettenPapierId; name = "Toilettenpapier"; categoryId = 12; quantity = 12.0; unit = "Rollen"; unitPrice = 0.3; kcalPerKg = $null; expiryDate = $null; locationId = 11; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
@{ id = $seifenId; name = "Seife"; categoryId = 12; quantity = 4.0; unit = "Stueck"; unitPrice = 1.5; kcalPerKg = $null; expiryDate = $null; locationId = 11; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
@{ id = $muesliId; name = "Muesliegel"; categoryId = 10; quantity = 20.0; unit = "Stueck"; unitPrice = 0.8; kcalPerKg = $null; expiryDate = "2028-03-31"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
@{ id = $apfelsaftId; name = "Apfelsaft"; categoryId = 11; quantity = 6.0; unit = "Liter"; unitPrice = 1.0; kcalPerKg = $null; expiryDate = "2028-09-30"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
@{ id = $kerzenId; name = "Kerzen"; categoryId = 10; quantity = 10.0; unit = "Stueck"; unitPrice = 1.5; kcalPerKg = $null; expiryDate = $null; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
@{ id = $pflasterId; name = "Erste-Hilfe-Pflaster"; categoryId = 12; quantity = 2.0; unit = "Packung"; unitPrice = 3.5; kcalPerKg = $null; expiryDate = "2029-06-30"; locationId = 11; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
)
settings = @()
}
# ---- Schritt 2: Bob pusht 10 Artikel ----
try {
$uploaded = Invoke-Api -Method PUT -Path "/api/inventory" -Body $bob10Items -Token $bobTokens.accessToken
Pass "Bob-Generaltest: 10 Artikel hochgeladen ($($uploaded.items.Count) Items)"
}
catch { Fail "Bob-Generaltest: PUT 10 Items fehlgeschlagen - $_" }
try {
$fetched = Invoke-Api -Method GET -Path "/api/inventory" -Token $bobTokens.accessToken
$itemCount = $fetched.items.Count
$catCount = $fetched.categories.Count
$locCount = $fetched.locations.Count
if ($itemCount -eq 10 -and $catCount -eq 3 -and $locCount -eq 2) {
Pass "Bob-Generaltest: GET liefert $itemCount Items, $catCount Kategorien, $locCount Lagerorte"
}
else {
Fail "Bob-Generaltest: Erwartet 10/3/2, erhalten $itemCount/$catCount/$locCount"
}
}
catch { Fail "Bob-Generaltest: GET nach PUT fehlgeschlagen - $_" }
# ---- Schritt 3: PATCH Dosenbrot qty 5 → 10 ----
try {
$patchBody = @{
id = $dosenBrotId; name = "Dosenbrot"; categoryId = 10
quantity = 10.0; unit = "Stueck"; unitPrice = 2.0
kcalPerKg = $null; expiryDate = "2028-12-31"; locationId = 10
notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
}
Invoke-Api -Method PATCH -Path "/api/inventory/items/$dosenBrotId" -Body $patchBody -Token $bobTokens.accessToken | Out-Null
$f = Invoke-Api -Method GET -Path "/api/inventory" -Token $bobTokens.accessToken
$dosenbrot = $f.items | Where-Object { $_.id -eq $dosenBrotId }
if ($dosenbrot -and $dosenbrot.quantity -eq 10.0) {
Pass "Bob-Generaltest: PATCH Dosenbrot qty=10 erfolgreich"
}
else {
Fail "Bob-Generaltest: PATCH Dosenbrot qty=$($dosenbrot.quantity), erwartet 10"
}
}
catch { Fail "Bob-Generaltest: PATCH fehlgeschlagen - $_" }
# ---- Schritt 4: PUT mit 11 Items (Salzcracker neu) ----
$salzcrackerId = [System.Guid]::NewGuid().ToString()
$bob11Items = @{
version = 1
categories = $bob10Items.categories
locations = $bob10Items.locations
items = $bob10Items.items + @(
@{ id = $salzcrackerId; name = "Salzcracker"; categoryId = 10; quantity = 5.0; unit = "Packung"; unitPrice = 1.2; kcalPerKg = $null; expiryDate = "2028-06-30"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
)
settings = @()
}
try {
Invoke-Api -Method PUT -Path "/api/inventory" -Body $bob11Items -Token $bobTokens.accessToken | Out-Null
$f = Invoke-Api -Method GET -Path "/api/inventory" -Token $bobTokens.accessToken
if ($f.items.Count -eq 11) {
Pass "Bob-Generaltest: PUT mit 11 Items - GET liefert 11 Items"
}
else {
Fail "Bob-Generaltest: Erwartet 11 Items, erhalten $($f.items.Count)"
}
}
catch { Fail "Bob-Generaltest: PUT 11 Items fehlgeschlagen - $_" }
# ---- Schritt 5: Server-seitige Aenderung loest WebSocket-Push aus ----
$bobWsMain = $null
try {
$bobWsMain = Open-WebSocket -token $bobTokens.accessToken
Start-Sleep -Milliseconds 800
# Zweiter Bob-Client patcht Dosenbrot → qty 15
$bobTokens2 = Invoke-Api -Method POST -Path "/api/auth/login" -Body @{ username = $BobUser; password = $BobPassword }
$patchBody2 = @{
id = $dosenBrotId; name = "Dosenbrot"; categoryId = 10
quantity = 15.0; unit = "Stueck"; unitPrice = 2.0
kcalPerKg = $null; expiryDate = "2028-12-31"; locationId = 10
notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
}
Invoke-Api -Method PATCH -Path "/api/inventory/items/$dosenBrotId" -Body $patchBody2 -Token $bobTokens2.accessToken | Out-Null
$events = Receive-WsMessages -ws $bobWsMain -waitSeconds 5
Info "WS-Events Schritt 5: $($events.Count) Event(s) - $($events | ConvertTo-Json -Compress)"
$invUpdated = @($events | Where-Object { $_.type -eq "inventoryUpdated" -and $_.itemId -eq $dosenBrotId })
if ($invUpdated.Count -gt 0) {
Pass "Bob-Generaltest: WS empfaengt inventoryUpdated-Event mit korrekter itemId"
}
else {
Fail "Bob-Generaltest: WS empfaengt kein inventoryUpdated-Event (events: $($events.Count), raw: $($events | ConvertTo-Json -Compress))"
}
}
catch { Fail "Bob-Generaltest: WebSocket-Push fehlgeschlagen - $_" }
finally {
if ($bobWsMain) { Close-WebSocket -ws $bobWsMain; $bobWsMain = $null }
}
# ---- Schritt 6: Full-Sync-Push bei PUT ----
$bobWsFull = $null
try {
$bobWsFull = Open-WebSocket -token $bobTokens.accessToken
Start-Sleep -Milliseconds 800
$bobTokens3 = Invoke-Api -Method POST -Path "/api/auth/login" -Body @{ username = $BobUser; password = $BobPassword }
Invoke-Api -Method PUT -Path "/api/inventory" -Body $bob11Items -Token $bobTokens3.accessToken | Out-Null
$events2 = Receive-WsMessages -ws $bobWsFull -waitSeconds 5
Info "WS-Events Schritt 6: $($events2.Count) Event(s) - $($events2 | ConvertTo-Json -Compress)"
$fullSync = @($events2 | Where-Object { $_.type -eq "fullSyncRequired" })
if ($fullSync.Count -gt 0) {
Pass "Bob-Generaltest: WS empfaengt fullSyncRequired-Event nach PUT"
}
else {
Fail "Bob-Generaltest: WS empfaengt kein fullSyncRequired-Event (events: $($events2.Count), raw: $($events2 | ConvertTo-Json -Compress))"
}
}
catch { Fail "Bob-Generaltest: Full-Sync-Push fehlgeschlagen - $_" }
finally {
if ($bobWsFull) { Close-WebSocket -ws $bobWsFull; $bobWsFull = $null }
}
# ---- T1: PATCH auf unbekannte Item-ID → 404 ----
try {
$unknownId = [System.Guid]::NewGuid().ToString()
$patchT1 = @{
id = $unknownId; name = "Unbekannt"; categoryId = 10
quantity = 1.0; unit = "Stueck"; unitPrice = 0.0
kcalPerKg = $null; expiryDate = $null; locationId = 10
notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
}
try {
Invoke-Api -Method PATCH -Path "/api/inventory/items/$unknownId" -Body $patchT1 -Token $bobTokens.accessToken | Out-Null
Fail "T1 bob_patchNonExistentItem: 404 erwartet, kein Fehler erhalten"
}
catch {
if ($_ -match "404") { Pass "T1 bob_patchNonExistentItem: PATCH unbekannte ID → 404" }
else { Fail "T1 bob_patchNonExistentItem: Unerwarteter Fehler: $_" }
}
}
catch { Fail "T1 bob_patchNonExistentItem: $_" }
# ---- T5: Bob sieht nicht Alices Inventar ----
if ($aliceTokens) {
try {
$aliceFetch = Invoke-Api -Method GET -Path "/api/inventory" -Token $aliceTokens.accessToken
$bobFetch = Invoke-Api -Method GET -Path "/api/inventory" -Token $bobTokens.accessToken
$aliceIds = @($aliceFetch.items | Select-Object -ExpandProperty id)
$bobIds = @($bobFetch.items | Select-Object -ExpandProperty id)
$overlap = $aliceIds | Where-Object { $bobIds -contains $_ }
if ($overlap.Count -eq 0) {
Pass "T5 bob_cannotSeeAlice_inventory: Bob und Alice haben separate Inventare"
}
else {
Fail "T5 bob_cannotSeeAlice_inventory: $($overlap.Count) Item-IDs ueberschneiden sich"
}
}
catch { Fail "T5 bob_cannotSeeAlice_inventory: $_" }
}
# ---- T6: Ungueltiger Token → 401 ----
try {
$invalidToken = "invalid.jwt.token"
try {
Invoke-Api -Method GET -Path "/api/inventory" -Token $invalidToken | Out-Null
Fail "T6 bob_invalidToken: GET sollte 401 liefern"
}
catch {
if ($_ -match "401") { Pass "T6 bob_invalidToken: GET /api/inventory → 401" }
else { Fail "T6 bob_invalidToken: Unerwarteter Fehler bei GET: $_" }
}
$t6PatchId = [System.Guid]::NewGuid().ToString()
$t6Body = @{ id = $t6PatchId; name = "X"; categoryId = 10; quantity = 1.0; unit = "x"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
try {
Invoke-Api -Method PATCH -Path "/api/inventory/items/$t6PatchId" -Body $t6Body -Token $invalidToken | Out-Null
Fail "T6 bob_invalidToken: PATCH sollte 401 liefern"
}
catch {
if ($_ -match "401") { Pass "T6 bob_invalidToken: PATCH /api/inventory/items → 401" }
else { Fail "T6 bob_invalidToken: Unerwarteter Fehler bei PATCH: $_" }
}
}
catch { Fail "T6 bob_invalidToken: $_" }
# ---- T2: PUT mit leeren Listen → GET liefert 0 Items ----
try {
$emptyInventory = @{ version = 1; categories = @(); locations = @(); items = @(); settings = @() }
Invoke-Api -Method PUT -Path "/api/inventory" -Body $emptyInventory -Token $bobTokens.accessToken | Out-Null
$emptyFetch = Invoke-Api -Method GET -Path "/api/inventory" -Token $bobTokens.accessToken
if ($emptyFetch.items.Count -eq 0) {
Pass "T2 bob_pushEmptyInventory: PUT leer → GET liefert 0 Items"
}
else {
Fail "T2 bob_pushEmptyInventory: Erwartet 0 Items, erhalten $($emptyFetch.items.Count)"
}
}
catch { Fail "T2 bob_pushEmptyInventory: $_" }
# ---- T3: Zwei PUTs hintereinander → Zweites ueberschreibt ----
try {
$firstId = [System.Guid]::NewGuid().ToString()
$secondId = [System.Guid]::NewGuid().ToString()
$firstPut = @{
version = 1
categories = @(@{ id = 20; name = "KatA" }); locations = @(@{ id = 20; name = "OrtA" })
items = @(@{ id = $firstId; name = "ErstesPUT"; categoryId = 20; quantity = 1.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 20; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() })
settings = @()
}
$secondPut = @{
version = 1
categories = @(@{ id = 21; name = "KatB" }); locations = @(@{ id = 21; name = "OrtB" })
items = @(@{ id = $secondId; name = "ZweitesPUT"; categoryId = 21; quantity = 2.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 21; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() })
settings = @()
}
Invoke-Api -Method PUT -Path "/api/inventory" -Body $firstPut -Token $bobTokens.accessToken | Out-Null
Invoke-Api -Method PUT -Path "/api/inventory" -Body $secondPut -Token $bobTokens.accessToken | Out-Null
$t3Fetch = Invoke-Api -Method GET -Path "/api/inventory" -Token $bobTokens.accessToken
$hasFirst = @($t3Fetch.items | Where-Object { $_.id -eq $firstId }).Count -gt 0
$hasSecond = @($t3Fetch.items | Where-Object { $_.id -eq $secondId }).Count -gt 0
if (-not $hasFirst -and $hasSecond) {
Pass "T3 bob_putOverwritesPreviousData: Zweites PUT ueberschreibt erstes"
}
else {
Fail "T3 bob_putOverwritesPreviousData: hasFirst=$hasFirst, hasSecond=$hasSecond"
}
}
catch { Fail "T3 bob_putOverwritesPreviousData: $_" }
# ---- T4: Bob patcht selbst → empfaengt trotzdem WS-Event ----
$t4WS = $null
try {
$t4ItemId = [System.Guid]::NewGuid().ToString()
$t4Inv = @{
version = 1; categories = @(@{ id = 30; name = "KatT4" }); locations = @(@{ id = 30; name = "OrtT4" })
items = @(@{ id = $t4ItemId; name = "T4-Item"; categoryId = 30; quantity = 1.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 30; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() })
settings = @()
}
Invoke-Api -Method PUT -Path "/api/inventory" -Body $t4Inv -Token $bobTokens.accessToken | Out-Null
$t4WS = Open-WebSocket -token $bobTokens.accessToken
Start-Sleep -Milliseconds 800
$t4Patch = @{ id = $t4ItemId; name = "T4-Item"; categoryId = 30; quantity = 5.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 30; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
Invoke-Api -Method PATCH -Path "/api/inventory/items/$t4ItemId" -Body $t4Patch -Token $bobTokens.accessToken | Out-Null
$t4Events = Receive-WsMessages -ws $t4WS -waitSeconds 5
Info "WS-Events T4: $($t4Events.Count) Event(s) - $($t4Events | ConvertTo-Json -Compress)"
if (@($t4Events | Where-Object { $_.type -eq "inventoryUpdated" }).Count -gt 0) {
Pass "T4 bob_websocketReceivesOwnPatch: Bob empfaengt inventoryUpdated nach eigenem PATCH"
}
else {
Fail "T4 bob_websocketReceivesOwnPatch: Kein WS-Event empfangen (events: $($t4Events.Count), raw: $($t4Events | ConvertTo-Json -Compress))"
}
}
catch { Fail "T4 bob_websocketReceivesOwnPatch: $_" }
finally {
if ($t4WS) { Close-WebSocket -ws $t4WS; $t4WS = $null }
}
# ---- T7: Bob oeffnet 2 WS-Verbindungen → beide empfangen Events ----
$t7ws1 = $null
$t7ws2 = $null
try {
$t7ItemId = [System.Guid]::NewGuid().ToString()
$t7Inv = @{
version = 1; categories = @(@{ id = 40; name = "KatT7" }); locations = @(@{ id = 40; name = "OrtT7" })
items = @(@{ id = $t7ItemId; name = "T7-Item"; categoryId = 40; quantity = 1.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 40; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() })
settings = @()
}
Invoke-Api -Method PUT -Path "/api/inventory" -Body $t7Inv -Token $bobTokens.accessToken | Out-Null
$t7ws1 = Open-WebSocket -token $bobTokens.accessToken
$t7ws2 = Open-WebSocket -token $bobTokens.accessToken
Start-Sleep -Milliseconds 1200
$bobTokens4 = Invoke-Api -Method POST -Path "/api/auth/login" -Body @{ username = $BobUser; password = $BobPassword }
$t7Patch = @{ id = $t7ItemId; name = "T7-Item"; categoryId = 40; quantity = 9.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 40; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
Invoke-Api -Method PATCH -Path "/api/inventory/items/$t7ItemId" -Body $t7Patch -Token $bobTokens4.accessToken | Out-Null
$t7ev1 = Receive-WsMessages -ws $t7ws1 -waitSeconds 5
$t7ev2 = Receive-WsMessages -ws $t7ws2 -waitSeconds 5
Info "WS-Events T7-WS1: $($t7ev1.Count) - $($t7ev1 | ConvertTo-Json -Compress)"
Info "WS-Events T7-WS2: $($t7ev2.Count) - $($t7ev2 | ConvertTo-Json -Compress)"
$got1 = @($t7ev1 | Where-Object { $_.type -eq "inventoryUpdated" }).Count -gt 0
$got2 = @($t7ev2 | Where-Object { $_.type -eq "inventoryUpdated" }).Count -gt 0
if ($got1 -and $got2) {
Pass "T7 bob_multipleWebSocketSessions: Beide WS-Sessions empfangen inventoryUpdated"
}
else {
Fail "T7 bob_multipleWebSocketSessions: WS1=$got1, WS2=$got2"
}
}
catch { Fail "T7 bob_multipleWebSocketSessions: $_" }
finally {
if ($t7ws1) { Close-WebSocket -ws $t7ws1; $t7ws1 = $null }
if ($t7ws2) { Close-WebSocket -ws $t7ws2; $t7ws2 = $null }
}
# ---- T8: Bob disconnected, dritter Client patcht → kein Server-Fehler ----
try {
$t8ItemId = [System.Guid]::NewGuid().ToString()
$t8Inv = @{
version = 1; categories = @(@{ id = 50; name = "KatT8" }); locations = @(@{ id = 50; name = "OrtT8" })
items = @(@{ id = $t8ItemId; name = "T8-Item"; categoryId = 50; quantity = 1.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 50; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() })
settings = @()
}
Invoke-Api -Method PUT -Path "/api/inventory" -Body $t8Inv -Token $bobTokens.accessToken | Out-Null
$t8ws = Open-WebSocket -token $bobTokens.accessToken
Close-WebSocket -ws $t8ws; $t8ws = $null
Start-Sleep -Milliseconds 600
$bobTokens5 = Invoke-Api -Method POST -Path "/api/auth/login" -Body @{ username = $BobUser; password = $BobPassword }
$t8Patch = @{ id = $t8ItemId; name = "T8-Item"; categoryId = 50; quantity = 7.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 50; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
try {
Invoke-Api -Method PATCH -Path "/api/inventory/items/$t8ItemId" -Body $t8Patch -Token $bobTokens5.accessToken | Out-Null
Pass "T8 bob_patchAfterDisconnect: PATCH nach Bob-Disconnect liefert keinen Server-Fehler"
}
catch { Fail "T8 bob_patchAfterDisconnect: PATCH fehlgeschlagen - $_" }
}
catch { Fail "T8 bob_patchAfterDisconnect: $_" }
}
# ---------------------------------------------------------------------------
# Ergebnis
# ---------------------------------------------------------------------------