package main import ( "bytes" "context" "net/http" "os" "testing" "time" "github.com/loyaly/behavision-desktop/internal/cloud" ) // The whole chain against the real head office and a real shop computer: // // TEST_CLOUD_EMAIL=... TEST_CLOUD_PASSWORD=... \ // go test ./desktop/ -run RemoteLive -v // // Everything in stream_remote_test.go proves the relay against a fake that // agrees with me. Only this proves the part that cannot be faked: that a shop // computer behind a router with no inbound route actually pushes frames when // asked, that they survive base64 and SSE, and that what comes out of the // loopback relay is a multipart stream an will paint. // // It also costs something to run, which is why it is opt-in: watching makes // the shop computer upload for as long as the test reads. func TestRemoteLiveFromProduction(t *testing.T) { email, pass := os.Getenv("TEST_CLOUD_EMAIL"), os.Getenv("TEST_CLOUD_PASSWORD") if email == "" || pass == "" { t.Skip("set TEST_CLOUD_EMAIL and TEST_CLOUD_PASSWORD to run against production") } base := os.Getenv("TEST_CLOUD_URL") if base == "" { base = "https://mcp.loyaly.ai" } ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) defer cancel() c := cloud.New(base) if _, err := c.Login(ctx, email, pass); err != nil { t.Fatalf("login: %v", err) } cams, err := c.RemoteCameras(ctx) if err != nil { t.Fatalf("cameras: %v", err) } t.Logf("%d cameras", len(cams)) target := os.Getenv("TEST_CLOUD_CAMERA") for _, cam := range cams { conn := "waiting" if cam.Connected != nil { conn = map[bool]string{true: "connected", false: "not connecting"}[*cam.Connected] } t.Logf(" %-10s %-16s %-15s snapshot=%v", cam.CameraID, cam.Site, conn, cam.Snapshot.Available) if target == "" && cam.Connected != nil && *cam.Connected { target = cam.CameraID } } if target == "" { t.Skip("no connected camera to watch") } p := newStreamProxy() if err := p.watchRemote(c.CameraLive); err != nil { t.Fatalf("watchRemote: %v", err) } defer p.stop() rctx, rcancel := context.WithTimeout(ctx, 30*time.Second) defer rcancel() req, _ := http.NewRequestWithContext(rctx, http.MethodGet, p.urlFor(target, "live.mjpeg"), nil) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("GET relay: %v", err) } defer resp.Body.Close() start := time.Now() acc, buf, frames := make([]byte, 0, 1<<20), make([]byte, 32*1024), 0 for frames < 10 { n, rerr := resp.Body.Read(buf) acc = append(acc, buf[:n]...) frames = bytes.Count(acc, []byte("--"+mjpegBoundary)) if rerr != nil { break } } el := time.Since(start) t.Logf("watching %q: %d frames, %d bytes, %.1fs (%.1f fps, %.0f KB/s)", target, frames, len(acc), el.Seconds(), float64(frames)/el.Seconds(), float64(len(acc))/el.Seconds()/1024) if frames < 3 { t.Fatalf("got %d frames from a connected camera - the shop computer is "+ "not answering head office's request to push", frames) } // Bytes that are actually a picture, not a framing header that happens to // be well formed. A JPEG begins FFD8. if !bytes.Contains(acc, []byte{0xFF, 0xD8, 0xFF}) { t.Error("no JPEG start marker anywhere in the stream") } }