package api import ( "context" "net/http" "net/http/httptest" "testing" "time" "github.com/loyaly/behavision-server/internal/auth" ) const liveCam = "22222222-3333-4444-5555-666666666666" // The whole cost argument for this feature: a camera nobody is watching must // cost a shop nothing at all. If Wanted were ever optimistic, every shop PC in // an estate would upload continuously. func TestNobodyWatchingMeansNothingIsWanted(t *testing.T) { h := NewLiveHub() if h.Wanted(liveCam) { t.Fatal("a camera nobody has asked for was reported as wanted") } if got := h.WantedAmong([]string{liveCam, "other"}); len(got) != 0 { t.Fatalf("wanted %v with no viewers", got) } } func TestAViewerMakesACameraWantedAndReleasingStopsIt(t *testing.T) { h := NewLiveHub() _, release := h.Watch(liveCam) if !h.Wanted(liveCam) { t.Fatal("a watched camera was not wanted") } release() if h.Wanted(liveCam) { t.Fatal("the camera stayed wanted after the last viewer left") } } // The opposite policy to the arrivals Hub, and deliberately so. There nothing // may be lost; here the only frame worth having is the newest one, and a queue // would show a viewer an ever-growing delay behind the shop instead of dropping // back to live. func TestASlowViewerGetsTheNewestFrameNotTheOldest(t *testing.T) { h := NewLiveHub() frames, release := h.Watch(liveCam) defer release() for _, f := range []string{"one", "two", "three"} { h.Publish(liveCam, []byte(f)) } select { case got := <-frames: if string(got) != "three" { t.Fatalf("a slow viewer was served %q, want the newest frame", got) } default: t.Fatal("nothing was delivered") } } // Publish reporting false is what stops the shop PC uploading. If it kept // saying true the agent would push into an empty room until the session cap. func TestPublishReportsWhenTheLastViewerHasGone(t *testing.T) { h := NewLiveHub() _, release := h.Watch(liveCam) if !h.Publish(liveCam, []byte("frame")) { t.Fatal("publish said to stop while somebody was watching") } release() if h.Publish(liveCam, []byte("frame")) { t.Fatal("publish did not say to stop after the last viewer left") } } // A browser that vanishes without saying so - the normal way a tab closes - // must stop the upload on its own. func TestInterestExpiresWithoutBeingRefreshed(t *testing.T) { h := NewLiveHub() frames, release := h.Watch(liveCam) defer release() _ = frames h.mu.Lock() h.cameras[liveCam].wanted = time.Now().Add(-time.Second) h.mu.Unlock() if h.Wanted(liveCam) { t.Fatal("interest did not lapse") } if h.Publish(liveCam, []byte("frame")) { t.Fatal("publish kept the shop uploading for a viewer that had gone") } } // The relay is keyed on a camera id and a hub does not know whose camera it is // holding, so ownership has to be proved before anything streams. Otherwise a // camera id - which is not a secret - would be enough to watch another // company's shop floor. func TestAnotherTenantCannotWatchYourCamera(t *testing.T) { srv, fs := newServer(t) fs.addCameraRef(liveCam, "client-1", "site-1", "cam1") rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/api/cameras/"+liveCam+"/live", nil) req = req.WithContext(context.WithValue(req.Context(), principalKey, auth.Principal{ClientID: "someone-else", Role: "owner"})) srv.handleWatchLive(rr, req) if rr.Code != http.StatusNotFound { t.Fatalf("status %d, want 404", rr.Code) } } // An agent may only push into its OWN site's camera. The agent supplies this // id, and a camera id is not a secret. func TestAnAgentCannotPushIntoAnotherSitesCamera(t *testing.T) { srv, fs := newServer(t) fs.addCameraRef(liveCam, "client-1", "site-1", "cam1") fs.addAgent("agent-token", AgentPrincipal{ClientID: "client-1", SiteID: "site-2"}) rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, "/api/agent/cameras/"+liveCam+"/live", nil) req.Header.Set("Authorization", "Bearer agent-token") srv.Routes().ServeHTTP(rr, req) if rr.Code != http.StatusNotFound { t.Fatalf("status %d, want 404", rr.Code) } }