package web import ( "log" "net/http" "net/http/httptest" "net/url" "strings" "sync" "testing" "time" ) func postCreate(s *Server, id string) *httptest.ResponseRecorder { form := url.Values{"customer_id": {id}, "customer_name": {"Tester"}, "email": {"t@felhom.example"}} req := httptest.NewRequest(http.MethodPost, "/configs/new", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") rr := httptest.NewRecorder() s.handleConfigCreate(rr, req) return rr } // R-728: a second submit for the same customer ID while the first is still between its duplicate check // and its save must NOT create (and mint) a second time. The first create is held at the save by the // test seam while the second runs to completion. func TestConfigCreate_ConcurrentSubmitCreatesOnce(t *testing.T) { s, _ := newTestServer(t) var logBuf syncBuf s.logger = log.New(&logBuf, "", 0) started, release := make(chan struct{}), make(chan struct{}) var once sync.Once s.beforeCreateSave = func(string) { first := false once.Do(func() { first = true }) if first { close(started) <-release } } done := make(chan *httptest.ResponseRecorder) go func() { done <- postCreate(s, "tester-2") }() select { case <-started: case <-time.After(5 * time.Second): t.Fatal("the first create never reached the save") } second := postCreate(s, "tester-2") close(release) first := <-done if first.Code != http.StatusSeeOther { t.Fatalf("first create = %d, want 303", first.Code) } if n := strings.Count(logBuf.String(), "Customer config created: tester-2"); n != 1 { t.Fatalf("customer created %d times for one press, want exactly 1:\n%s", n, logBuf.String()) } if !strings.Contains(second.Body.String(), "already being created") { t.Errorf("the second submit must be told a create is in progress; got %d", second.Code) } // The guard is released: a later create of the SAME id is refused by the ordinary duplicate check, // not stuck behind the in-flight guard. s.beforeCreateSave = nil if body := postCreate(s, "tester-2").Body.String(); !strings.Contains(body, "already exists") { t.Error("after the first create returned, a re-submit must meet the ordinary 'already exists' check") } } // syncBuf is a goroutine-safe log sink. type syncBuf struct { mu sync.Mutex b strings.Builder } func (w *syncBuf) Write(p []byte) (int, error) { w.mu.Lock(); defer w.mu.Unlock(); return w.b.Write(p) } func (w *syncBuf) String() string { w.mu.Lock(); defer w.mu.Unlock(); return w.b.String() }