activator: prove the beacon test isn't vacuous against an asleep route
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 file changed,
+66
−0
internal/activator/activator_test.go+66 −0
diff --git a/internal/activator/activator_test.go b/internal/activator/activator_test.go| index 8ead7934a..1e2ce13b7 100644 |
| --- a/internal/activator/activator_test.go |
| +++ b/internal/activator/activator_test.go |
| @@ -4035,3 +4035,69 @@ func TestBrowserBeaconDoesNotStampActivity(t *testing.T) { |
| t.Errorf("a beacon started an instance") |
| } |
| } |
| + |
| +// TestBrowserBeaconDoesNotStampActivity above proves a beacon leaves an |
| +// ALREADY-awake route's lastActive alone, but arrangeAwake means |
| +// a.wake() was never reachable there regardless of what the edge does โ |
| +// that assertion is vacuous on its own. This test closes the gap against |
| +// a route that starts with no live entry at all: a beacon must not create |
| +// one (spec ยง4), and a trailing ordinary request proves the fixture would |
| +// have shown a wake if the beacon had triggered one. |
| +func TestBrowserBeaconDoesNotWakeAsleepRoute(t *testing.T) { |
| + const host = "sleepbeacon.bab.oncarlos.com" |
| + a, b, _, reg := testActivator(t, time.Hour) |
| + rt := testRoute(host) |
| + if err := reg.Upsert(rt); err != nil { |
| + t.Fatalf("Upsert %s: %v", rt.Host, err) |
| + } |
| + a.mu.Lock() |
| + if a.entries[rt.Host] != nil { |
| + t.Fatal("route already has a live entry before any request") |
| + } |
| + a.mu.Unlock() |
| + _, _, starts0 := b.counts() |
| + |
| + e, err := edge.New(reg, a) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + rec := httptest.NewRecorder() |
| + req := httptest.NewRequest("POST", "https://"+host+"/__carlos/a", strings.NewReader(`{"name":"n"}`)) |
| + req.Host = host |
| + req.Header.Set("Origin", "https://"+host) |
| + e.ServeHTTP(rec, req) |
| + if rec.Code != http.StatusAccepted { |
| + t.Fatalf("status %d", rec.Code) |
| + } |
| + |
| + a.mu.Lock() |
| + stillAsleep := a.entries[rt.Host] == nil |
| + a.mu.Unlock() |
| + if !stillAsleep { |
| + t.Error("a beacon woke a sleeping route") |
| + } |
| + if _, _, starts := b.counts(); starts != starts0 { |
| + t.Error("a beacon started an instance") |
| + } |
| + |
| + // Not vacuous: an ORDINARY request to the same asleep route reaches the |
| + // activator's begin(), which creates a.entries[host] synchronously and |
| + // then runs wake() โ with the fake backend defaulting to no errors, that |
| + // wake succeeds, so the entry ends up awake AND the backend's start |
| + // count moves. Either signal moving proves this fixture (route, edge, |
| + // backend) is capable of showing a wake; the beacon case above staying |
| + // put is therefore the reserved path's doing, not an activator that |
| + // never wakes anything. |
| + rec2 := httptest.NewRecorder() |
| + req2 := httptest.NewRequest("GET", "https://"+host+"/", nil) |
| + req2.Host = host |
| + e.ServeHTTP(rec2, req2) |
| + |
| + a.mu.Lock() |
| + woke := a.entries[rt.Host] != nil |
| + a.mu.Unlock() |
| + _, _, starts2 := b.counts() |
| + if !woke && starts2 == starts0 { |
| + t.Fatal("an ordinary request to the asleep route neither woke it nor started an instance; this fixture can't distinguish a real intercept from a broken wake") |
| + } |
| +} |