128 lines
4.9 KiB
Go
128 lines
4.9 KiB
Go
package web
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
|
)
|
|
|
|
// R-1028: the claim box renders only when the household still has to give the code — render test
|
|
// per branch (the seam-wiring rule).
|
|
//
|
|
// v0.315.0: the box is no longer Plex-specific markup behind a `.PlexClaim` flag. It is the GENERIC
|
|
// after-start card, and the thing it branches on is whether afterStartFields put the field in the
|
|
// page data at all — which is where the „registered action AND Needed()" decision now lives.
|
|
// COMPANION RED-PROOF: render the card unconditionally (drop the range over .AfterStartFields) →
|
|
// the two negative cases fail. Restored.
|
|
func TestPlexClaimBox_RendersPerBranch(t *testing.T) {
|
|
st := lcStack("plex", "", true)
|
|
st.State = stacks.StateRunning
|
|
claimField := stacks.DeployField{EnvVar: "PLEX_CLAIM", Label: "Plex kod", AfterStart: true,
|
|
Placeholder: "claim-xxxxxxxxxxxxxxxxxxxx"}
|
|
for _, c := range []struct {
|
|
name string
|
|
fields []stacks.DeployField
|
|
want bool
|
|
}{
|
|
{"the code is still wanted", []stacks.DeployField{claimField}, true},
|
|
{"nothing is wanted", nil, false},
|
|
} {
|
|
d := appInfoData(st)
|
|
d["AfterStartFields"] = c.fields
|
|
h := renderBackupPage(t, "app_settings", d)
|
|
if got := strings.Contains(h, `data-afterstart="PLEX_CLAIM"`) && strings.Contains(h, `id="after-start-PLEX_CLAIM"`); got != c.want {
|
|
t.Errorf("%s: claim box shown=%v, want %v", c.name, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The code is checked for its shape before any call: no call for something that is not a claim code.
|
|
func TestPlexClaimCodeShape(t *testing.T) {
|
|
for code, ok := range map[string]bool{"claim-AbCdEf123456789_-xyz": true, "claim-short": false, "claim-abcdefgh": true, "claim-": false,
|
|
"https://plex.tv/claim": false, "claim-abc def": false, "": false} {
|
|
if got := plexClaimCode.MatchString(code); got != ok {
|
|
t.Errorf("%q: accepted=%v, want %v", code, got, ok)
|
|
}
|
|
}
|
|
}
|
|
|
|
// R-1045: the household's „Kész" never opens an UNCLAIMED Plex (it answers everything to a private address with no
|
|
// token); once Plex says claimed, the press opens it. No template probe is needed (an old controller would refuse the
|
|
// press forever with one). COMPANION RED-PROOF: drop the plexClaimed check in appSetupGateOpenHandler → the first press
|
|
// opens the gate (200). Restored.
|
|
func TestPlexDonePress_RefusedWhileUnclaimed(t *testing.T) {
|
|
s := gateHarness(t)
|
|
app := filepath.Join(s.cfg.Paths.StacksDir, "gapp")
|
|
if err := os.WriteFile(filepath.Join(app, ".felhom.yml"), []byte("display_name: Plex\nslug: plex\nsetup_gate: true\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.stackMgr.ScanStacks(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
claimed := false
|
|
old := plexClaimed
|
|
plexClaimed = func() bool { return claimed }
|
|
defer func() { plexClaimed = old }()
|
|
press := func() int {
|
|
w := httptest.NewRecorder()
|
|
s.appSetupGateOpenHandler(w, httptest.NewRequest(http.MethodPost, "/apps/plex/setup-gate/open", nil), "plex")
|
|
return w.Code
|
|
}
|
|
if c := press(); c != http.StatusConflict {
|
|
t.Fatalf("„Kész” on an unclaimed Plex: HTTP %d, want 409 — the gate must stay closed", c)
|
|
}
|
|
claimed = true
|
|
if c := press(); c != http.StatusOK {
|
|
t.Fatalf("„Kész” on a claimed Plex: HTTP %d, want 200", c)
|
|
}
|
|
}
|
|
|
|
// R-1065: a claim that Plex confirms schedules the media wiring (which sets the libraries — it could
|
|
// not while Plex was unclaimed); a claim Plex refuses, or one Plex never reports as claimed, does not.
|
|
// The consequence asserted is the armed wiring timer — the same one a media app coming up arms.
|
|
// COMPANION RED-PROOF: drop the RunMediaWiringSoon call in applyPlexClaim → the claimed case fails.
|
|
func TestPlexClaim_TriggersMediaWiring(t *testing.T) {
|
|
oldClaim, oldClaimed, oldPoll := plexClaim, plexClaimed, plexClaimPoll
|
|
t.Cleanup(func() { plexClaim, plexClaimed, plexClaimPoll = oldClaim, oldClaimed, oldPoll })
|
|
plexClaimPoll = 0
|
|
for _, c := range []struct {
|
|
name string
|
|
status int
|
|
claimed bool
|
|
wantErr bool
|
|
wantWiring bool
|
|
}{
|
|
{"claimed", http.StatusOK, true, false, true},
|
|
{"plex refused the code", http.StatusUnauthorized, true, true, false},
|
|
{"plex never says claimed", http.StatusOK, false, true, false},
|
|
} {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := gateHarness(t)
|
|
t.Cleanup(func() {
|
|
s.mediaMu.Lock()
|
|
if s.mediaSoonTmr != nil {
|
|
s.mediaSoonTmr.Stop()
|
|
}
|
|
s.mediaMu.Unlock()
|
|
})
|
|
plexClaim = func(string) (int, error) { return c.status, nil }
|
|
plexClaimed = func() bool { return c.claimed }
|
|
err := s.applyPlexClaim("claim-AbCdEf123456789xyz")
|
|
if (err != nil) != c.wantErr {
|
|
t.Fatalf("applyPlexClaim err=%v, want error=%v", err, c.wantErr)
|
|
}
|
|
s.mediaMu.Lock()
|
|
armed := s.mediaSoonTmr != nil
|
|
s.mediaMu.Unlock()
|
|
if armed != c.wantWiring {
|
|
t.Errorf("media wiring scheduled=%v, want %v", armed, c.wantWiring)
|
|
}
|
|
})
|
|
}
|
|
}
|