61 lines
1.8 KiB
Go
61 lines
1.8 KiB
Go
package test
|
|
|
|
import (
|
|
"RenewCMS/api/pkg/apperr"
|
|
"net/http"
|
|
"strconv"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
const concurrentSaveRounds = 20
|
|
|
|
func TestArticleConcurrentSavesHTTP(t *testing.T) {
|
|
t.Run("Two saves of the same article with the same if_revision: one wins, the other conflicts", func(t *testing.T) {
|
|
for round := 0; round < concurrentSaveRounds; round++ {
|
|
w := newStoryWorld(t)
|
|
revision, _ := revisionsOf(w, 10)
|
|
bodies := []string{"<p>Mine</p>", "<p>Theirs</p>"}
|
|
responses := make([]storyResponse, len(bodies))
|
|
start := make(chan struct{})
|
|
var saves sync.WaitGroup
|
|
for i, body := range bodies {
|
|
saves.Add(1)
|
|
go func() {
|
|
defer saves.Done()
|
|
<-start
|
|
responses[i] = w.send(storyUpdater, http.MethodPatch, "/v1/article/10", `{"body":"`+body+`","if_revision":`+strconv.FormatUint(uint64(revision), 10)+`}`)
|
|
}()
|
|
}
|
|
close(start)
|
|
saves.Wait()
|
|
|
|
winners, losers := 0, 0
|
|
var winningBody string
|
|
for i, response := range responses {
|
|
switch response.Code {
|
|
case http.StatusOK:
|
|
winners++
|
|
winningBody = bodies[i]
|
|
case http.StatusConflict:
|
|
losers++
|
|
if response.text() != apperr.RevisionConflictErrorMessage+"\n" {
|
|
t.Fatalf("conflict body %q", response.text())
|
|
}
|
|
if response.header(apperr.CurrentRevisionHeader) != strconv.FormatUint(uint64(revision+1), 10) {
|
|
t.Fatalf("current revision header %q", response.header(apperr.CurrentRevisionHeader))
|
|
}
|
|
default:
|
|
t.Fatalf("unexpected %d %q", response.Code, response.text())
|
|
}
|
|
}
|
|
if winners != 1 || losers != 1 {
|
|
t.Fatalf("round %d: %d saved, %d conflicted", round, winners, losers)
|
|
}
|
|
if stored, _ := revisionsOf(w, 10); stored != revision+1 || w.stored(10).Body != winningBody {
|
|
t.Fatalf("round %d: stored revision %d body %q, the winner sent %q", round, stored, w.stored(10).Body, winningBody)
|
|
}
|
|
}
|
|
})
|
|
}
|