package api import ( "encoding/json" "fmt" "net/http" "net/http/httptest" "strings" "sync" "testing" ) // TestBurnAfterReadConcurrentRace is the #58 regression test: N concurrent // readers of a burn-after-read paste must receive exactly one success with // content; every other reader must get 404 and never any content. func TestBurnAfterReadConcurrentRace(t *testing.T) { s := testServer(t) h := s.routes() id := createBurnReads(t, h, 1) const readers = 24 var wg sync.WaitGroup var mu sync.Mutex wins, losses := 0, 0 for i := 0; i < readers; i++ { wg.Add(1) go func(i int) { defer wg.Done() req := httptest.NewRequest("GET", "/api/pastes/"+id, nil) req.AddCookie(&http.Cookie{Name: "vwr", Value: fmt.Sprintf("racer-%d", i)}) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) mu.Lock() defer mu.Unlock() if rec.Code == 200 { wins++ var got struct { Content string `json:"content"` } if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil || got.Content != "limited" { t.Errorf("winning read returned wrong content: %v %q", err, got.Content) } } else if rec.Code == 404 { losses++ if strings.Contains(rec.Body.String(), "limited") { t.Errorf("losing read leaked content: %s", rec.Body.String()) } } else { t.Errorf("unexpected status %d: %s", rec.Code, rec.Body.String()) } }(i) } wg.Wait() if wins != 1 { t.Fatalf("expected exactly 1 winning read of burn paste, got %d (losses=%d)", wins, losses) } if losses != readers-1 { t.Fatalf("expected %d losing reads, got %d", readers-1, losses) } } // TestBurnAfterNReadsConcurrentBudget hammers a burn-after-N paste with many // more concurrent distinct readers than the budget: total admissions must // equal exactly N, and no losing read may see content. func TestBurnAfterNReadsConcurrentBudget(t *testing.T) { s := testServer(t) h := s.routes() const budget = 3 id := createBurnReads(t, h, budget) const readers = 30 var wg sync.WaitGroup var mu sync.Mutex wins := 0 for i := 0; i < readers; i++ { wg.Add(1) go func(i int) { defer wg.Done() req := httptest.NewRequest("GET", "/api/pastes/"+id, nil) req.AddCookie(&http.Cookie{Name: "vwr", Value: fmt.Sprintf("racer-%d", i)}) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) mu.Lock() defer mu.Unlock() if rec.Code == 200 { wins++ } else if rec.Code != 404 { t.Errorf("unexpected status %d: %s", rec.Code, rec.Body.String()) } }(i) } wg.Wait() if wins != budget { t.Fatalf("expected exactly %d admitted reads, got %d", budget, wins) } // After the race, the paste is burned for everyone. if rec := getWithCookie(t, h, id, "after-the-fact"); rec.Code != 404 { t.Fatalf("paste should be burned after budget exhausted, got %d", rec.Code) } }