Files
felhom.eu/hub/internal/web/r728_create_once_test.go
T

79 lines
2.5 KiB
Go

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() }