Files
Behavision/desktop/icons_test.go
Suriyakumarvijayanayagam dad04e8cda Behavision: face recognition for retail, edge to head office
Five components that ship as one product:

- behavision/  the recognition engine. RTSP ingest, YuNet detection, IoU
               tracking, ArcFace embeddings, a FAISS/SQLite gallery, and a
               FastAPI dashboard. Identity is decided once per TRACK from an
               average of at least three embeddings, never per frame.
- agent/       the Go edge agent: supervises the engine, holds a durable
               spool, and drains it to MQTT. Nothing is acked before the
               broker confirms.
- desktop/     the shop PC application (Wails + React + tray).
- server/      the cloud API, MQTT consumer, reports and assistant.
- web/         platform.loyaly.ai, the head-office app, embedded in the
               server binary.

The gallery stores 512-float embeddings and timestamps - no images unless
`app.store_faces` is switched on. Those embeddings are biometric personal
data under GDPR and India's DPDP: template inversion reconstructs a
recognisable face from an ArcFace vector, so data/behavision.db is treated
as a biometric database and DELETE /api/visitors/{id} is a real erasure.

CLAUDE.md carries the reasoning behind every non-obvious decision here,
including the ones that were measured and the ones that were wrong first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HViLj9gYNRtSr7YVZmW5sn
2026-09-04 11:14:18 +05:30

94 lines
3.2 KiB
Go

package main
import (
"bytes"
"encoding/binary"
"image/png"
"runtime"
"testing"
)
// The tray icon shipped as a PNG for a while. systray hands the bytes to
// LoadImageW, which decodes .ico only, so Windows logged one line and drew
// nothing — and nothing on a Mac could notice. These tests are the substitute
// for the Windows box we do not have.
func TestEncodeICOIsAValidIconFile(t *testing.T) {
b := encodeICO(circle(colorFor("ok")))
if len(b) < 22 {
t.Fatalf("far too short: %d bytes", len(b))
}
if got := b[:6]; !bytes.Equal(got, []byte{0, 0, 1, 0, 1, 0}) {
t.Errorf("ICONDIR = % x, want 00 00 01 00 01 00", got)
}
if b[6] != iconSize || b[7] != iconSize {
t.Errorf("entry is %dx%d, want %dx%d", b[6], b[7], iconSize, iconSize)
}
if bpp := binary.LittleEndian.Uint16(b[12:14]); bpp != 32 {
t.Errorf("bits per pixel = %d, want 32", bpp)
}
// A wrong length here loads as a truncated or garbage icon rather than
// failing outright, which is the harder version of this bug to spot.
size := binary.LittleEndian.Uint32(b[14:18])
off := binary.LittleEndian.Uint32(b[18:22])
if off != 22 {
t.Errorf("image offset = %d, want 22", off)
}
if int(off)+int(size) != len(b) {
t.Errorf("entry claims %d bytes at %d, file is %d", size, off, len(b))
}
if h := binary.LittleEndian.Uint32(b[22:26]); h != 40 {
t.Errorf("BITMAPINFOHEADER size = %d, want 40", h)
}
// Height must be doubled for the implied AND mask or Windows draws the
// bottom half of the icon stretched over the whole square.
if hh := int32(binary.LittleEndian.Uint32(b[30:34])); hh != int32(iconSize*2) {
t.Errorf("biHeight = %d, want %d", hh, iconSize*2)
}
}
func TestEncodeICOPixelsAreBGRABottomUp(t *testing.T) {
want := colorFor("error") // red: distinguishable from B and G if swapped
img := circle(want)
b := encodeICO(img)
// Centre of the circle, which is solid fill. Bottom-up means image row
// iconSize/2 lands at DIB row iconSize/2-1 counting from the start.
row := iconSize - 1 - iconSize/2
i := 22 + 40 + (row*iconSize+iconSize/2)*4
got := b[i : i+4]
if !bytes.Equal(got, []byte{want.B, want.G, want.R, 0xFF}) {
t.Errorf("centre pixel = % x, want % x (BGRA)",
got, []byte{want.B, want.G, want.R, 0xFF})
}
}
func TestIconForMatchesThePlatformDecoder(t *testing.T) {
b := iconFor("ok")
pngMagic := []byte{0x89, 'P', 'N', 'G'}
if runtime.GOOS == "windows" {
if bytes.HasPrefix(b, pngMagic) {
t.Fatal("Windows tray icon is a PNG; LoadImageW will refuse it")
}
if !bytes.HasPrefix(b, []byte{0, 0, 1, 0}) {
t.Fatalf("Windows tray icon is not an ICO: % x", b[:4])
}
return
}
if _, err := png.Decode(bytes.NewReader(b)); err != nil {
t.Fatalf("non-Windows tray icon is not a decodable PNG: %v", err)
}
}
// The four states exist to be told apart at a glance; identical bytes would
// mean the icon never changes and the amber "running but blind" state — the
// one that otherwise goes unnoticed for weeks — looks exactly like healthy.
func TestEveryStateLooksDifferent(t *testing.T) {
seen := map[string]string{}
for _, s := range []string{"ok", "warn", "error", "stopped"} {
k := string(iconFor(s))
if prev, dup := seen[k]; dup {
t.Errorf("%q and %q render identically", prev, s)
}
seen[k] = s
}
}