Heavy security and file splitting
CI / Go Tests (push) Successful in 15s
CI / Build (push) Successful in 9s
Format / gofmt (push) Successful in 7s
Release Artifacts / Validate release tag (push) Successful in 2s
Release Artifacts / Build and release executables (push) Successful in 15s
Release Artifacts / Build and release Docker image (push) Successful in 28s
CI / Go Tests (push) Successful in 15s
CI / Build (push) Successful in 9s
Format / gofmt (push) Successful in 7s
Release Artifacts / Validate release tag (push) Successful in 2s
Release Artifacts / Build and release executables (push) Successful in 15s
Release Artifacts / Build and release Docker image (push) Successful in 28s
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConcurrentUploadsRemainConsistentAndReadable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := newTestHandler(t, testServerOptions{
|
||||
maxUploadBytes: 1024 * 1024,
|
||||
defaultTTL: time.Hour,
|
||||
rateLimitEnable: false,
|
||||
})
|
||||
|
||||
const uploads = 80
|
||||
|
||||
type createdScratch struct {
|
||||
id string
|
||||
body string
|
||||
}
|
||||
|
||||
resultsCh := make(chan createdScratch, uploads)
|
||||
errCh := make(chan error, uploads)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < uploads; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
body := fmt.Sprintf("parallel-upload-%03d-%s", i, strings.Repeat("x", i%17))
|
||||
createReq := httptest.NewRequest(http.MethodPost, "/api/scratch", bytes.NewBufferString(body))
|
||||
createReq.Header.Set("Content-Type", "text/plain; charset=utf-8")
|
||||
createReq.RemoteAddr = fmt.Sprintf("127.0.0.1:%d", 8200+i)
|
||||
createRec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(createRec, createReq)
|
||||
if createRec.Code != http.StatusCreated {
|
||||
errCh <- fmt.Errorf("create status = %d, want %d", createRec.Code, http.StatusCreated)
|
||||
return
|
||||
}
|
||||
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal(createRec.Body.Bytes(), &payload); err != nil {
|
||||
errCh <- fmt.Errorf("decode create payload error: %w", err)
|
||||
return
|
||||
}
|
||||
id, _ := payload["id"].(string)
|
||||
if id == "" {
|
||||
errCh <- fmt.Errorf("create payload missing id")
|
||||
return
|
||||
}
|
||||
|
||||
metaReq := httptest.NewRequest(http.MethodGet, "/api/scratch/"+id, nil)
|
||||
metaRec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(metaRec, metaReq)
|
||||
if metaRec.Code != http.StatusOK {
|
||||
errCh <- fmt.Errorf("metadata status = %d, want %d", metaRec.Code, http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
rawReq := httptest.NewRequest(http.MethodGet, "/api/raw/"+id, nil)
|
||||
rawRec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rawRec, rawReq)
|
||||
if rawRec.Code != http.StatusOK {
|
||||
errCh <- fmt.Errorf("raw status = %d, want %d", rawRec.Code, http.StatusOK)
|
||||
return
|
||||
}
|
||||
if got := rawRec.Body.String(); got != body {
|
||||
errCh <- fmt.Errorf("raw body mismatch = %q, want %q", got, body)
|
||||
return
|
||||
}
|
||||
|
||||
resultsCh <- createdScratch{id: id, body: body}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(resultsCh)
|
||||
close(errCh)
|
||||
for err := range errCh {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
seen := make(map[string]struct{}, uploads)
|
||||
for result := range resultsCh {
|
||||
if _, exists := seen[result.id]; exists {
|
||||
t.Errorf("duplicate scratch id generated: %s", result.id)
|
||||
continue
|
||||
}
|
||||
seen[result.id] = struct{}{}
|
||||
}
|
||||
if len(seen) != uploads {
|
||||
t.Fatalf("unique IDs = %d, want %d", len(seen), uploads)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParallelClientsCanReadSameScratchAcrossAllReadRoutes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := newTestHandler(t, testServerOptions{
|
||||
maxUploadBytes: 1024,
|
||||
defaultTTL: time.Hour,
|
||||
rateLimitEnable: false,
|
||||
})
|
||||
|
||||
const body = "parallel readers"
|
||||
createReq := httptest.NewRequest(http.MethodPost, "/api/scratch", bytes.NewBufferString(body))
|
||||
createReq.Header.Set("Content-Type", "text/plain; charset=utf-8")
|
||||
createReq.RemoteAddr = "127.0.0.1:6010"
|
||||
createRec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(createRec, createReq)
|
||||
if createRec.Code != http.StatusCreated {
|
||||
t.Fatalf("create status = %d, want %d", createRec.Code, http.StatusCreated)
|
||||
}
|
||||
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal(createRec.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("decode create payload error = %v", err)
|
||||
}
|
||||
id, _ := payload["id"].(string)
|
||||
if id == "" {
|
||||
t.Fatalf("create payload missing id")
|
||||
}
|
||||
|
||||
paths := []string{
|
||||
"/api/raw/" + id,
|
||||
"/s/" + id,
|
||||
"/api/scratch/" + id,
|
||||
}
|
||||
const requests = 120
|
||||
|
||||
errCh := make(chan error, requests)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < requests; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
path := paths[i%len(paths)]
|
||||
req := httptest.NewRequest(http.MethodGet, path, nil)
|
||||
req.RemoteAddr = fmt.Sprintf("127.0.0.1:%d", 6100+i)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
errCh <- fmt.Errorf("path %s status = %d, want %d", path, rec.Code, http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(path, "/api/raw/"):
|
||||
if got := rec.Body.String(); got != body {
|
||||
errCh <- fmt.Errorf("raw body = %q, want %q", got, body)
|
||||
}
|
||||
case strings.HasPrefix(path, "/s/"):
|
||||
if !strings.Contains(rec.Body.String(), `id="app"`) {
|
||||
errCh <- fmt.Errorf("view body missing SPA marker")
|
||||
}
|
||||
case strings.HasPrefix(path, "/api/scratch/"):
|
||||
var gotPayload map[string]any
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &gotPayload); err != nil {
|
||||
errCh <- fmt.Errorf("metadata decode error: %w", err)
|
||||
return
|
||||
}
|
||||
if gotID, _ := gotPayload["id"].(string); gotID != id {
|
||||
errCh <- fmt.Errorf("metadata id = %q, want %q", gotID, id)
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
close(errCh)
|
||||
for err := range errCh {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParallelClientsExpiredScratchReadsReturnGone(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := newTestHandler(t, testServerOptions{
|
||||
maxUploadBytes: 1024,
|
||||
defaultTTL: 80 * time.Millisecond,
|
||||
rateLimitEnable: false,
|
||||
})
|
||||
|
||||
createReq := httptest.NewRequest(http.MethodPost, "/api/scratch", bytes.NewBufferString("expires soon"))
|
||||
createReq.Header.Set("Content-Type", "text/plain; charset=utf-8")
|
||||
createReq.RemoteAddr = "127.0.0.1:7010"
|
||||
createRec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(createRec, createReq)
|
||||
if createRec.Code != http.StatusCreated {
|
||||
t.Fatalf("create status = %d, want %d", createRec.Code, http.StatusCreated)
|
||||
}
|
||||
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal(createRec.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("decode create payload error = %v", err)
|
||||
}
|
||||
id, _ := payload["id"].(string)
|
||||
if id == "" {
|
||||
t.Fatalf("create payload missing id")
|
||||
}
|
||||
|
||||
time.Sleep(130 * time.Millisecond)
|
||||
|
||||
paths := []string{
|
||||
"/api/raw/" + id,
|
||||
"/s/" + id,
|
||||
"/api/scratch/" + id,
|
||||
}
|
||||
const requests = 90
|
||||
|
||||
errCh := make(chan error, requests)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < requests; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
path := paths[i%len(paths)]
|
||||
req := httptest.NewRequest(http.MethodGet, path, nil)
|
||||
req.RemoteAddr = fmt.Sprintf("127.0.0.1:%d", 7100+i)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusGone {
|
||||
errCh <- fmt.Errorf("expired path %s status = %d, want %d", path, rec.Code, http.StatusGone)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(path, "/s/") && !strings.Contains(rec.Body.String(), `id="app"`) {
|
||||
errCh <- fmt.Errorf("expired view body missing SPA marker")
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
close(errCh)
|
||||
for err := range errCh {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user