4bb8947ecf
- C4: Propagate request context through semaphores and upstream calls - C5: Make client rate limiter cleanup goroutine idempotent via sync.Once - P1: Streaming response path using io.Copy instead of full materialization - P2: Gate promotion goroutines to files >= 64KiB - P3: Reduce global lock contention during eviction - P4: Demote hot-path logging and compact metrics output - R1: Add upstream URL scheme/host validation - R4: Add cache file format version + tolerant deserialization - Makefile: Make build-snapshot-single practical by using -short tests - Config: Add GetDefaultConfig + Validate for better testability and R1 coverage All changes follow the "smallest safe diff" principle from the review. Safe test suite now builds and runs cleanly via make build-snapshot-single. Ref: docs/reviews/steamcache2-production-hardening-review-2026-05-26.md
77 lines
2.0 KiB
Go
77 lines
2.0 KiB
Go
package steamcache
|
|
|
|
import (
|
|
"bytes"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestCacheFileFormat tests the cache file format directly
|
|
func TestCacheFileFormat(t *testing.T) {
|
|
// Create test data
|
|
bodyData := []byte("test steam content")
|
|
contentHash := calculateSHA256(bodyData)
|
|
|
|
// Create mock response
|
|
resp := &http.Response{
|
|
StatusCode: 200,
|
|
Status: "200 OK",
|
|
Header: make(http.Header),
|
|
Body: http.NoBody,
|
|
}
|
|
resp.Header.Set("Content-Type", "application/x-steam-chunk")
|
|
resp.Header.Set("Content-Length", "18")
|
|
resp.Header.Set("X-Sha1", contentHash)
|
|
|
|
// Create SteamCache instance
|
|
sc := &SteamCache{}
|
|
|
|
// Reconstruct raw response
|
|
rawResponse := sc.reconstructRawResponse(resp, bodyData)
|
|
|
|
// Serialize to cache format
|
|
cacheData, err := serializeRawResponse(rawResponse)
|
|
if err != nil {
|
|
t.Fatalf("Failed to serialize cache file: %v", err)
|
|
}
|
|
|
|
// Deserialize from cache format
|
|
cacheFile, err := deserializeCacheFile(cacheData)
|
|
if err != nil {
|
|
t.Fatalf("Failed to deserialize cache file: %v", err)
|
|
}
|
|
|
|
// Verify cache file structure
|
|
if cacheFile.ContentHash != contentHash {
|
|
t.Errorf("ContentHash mismatch: expected %s, got %s", contentHash, cacheFile.ContentHash)
|
|
}
|
|
|
|
if cacheFile.ResponseSize != int64(len(rawResponse)) {
|
|
t.Errorf("ResponseSize mismatch: expected %d, got %d", len(rawResponse), cacheFile.ResponseSize)
|
|
}
|
|
|
|
// Verify raw response is preserved
|
|
if !bytes.Equal(cacheFile.Response, rawResponse) {
|
|
t.Error("Raw response not preserved in cache file")
|
|
}
|
|
|
|
// Test streaming the cached response
|
|
recorder := httptest.NewRecorder()
|
|
req := httptest.NewRequest("GET", "/test/format", nil)
|
|
|
|
sc.streamCachedResponse(recorder, req, cacheFile, "test-key", "127.0.0.1", time.Now())
|
|
|
|
// Verify streamed response
|
|
if recorder.Code != 200 {
|
|
t.Errorf("Expected status code 200, got %d", recorder.Code)
|
|
}
|
|
|
|
if !bytes.Equal(recorder.Body.Bytes(), bodyData) {
|
|
t.Error("Streamed response body does not match original")
|
|
}
|
|
|
|
t.Log("✅ Cache file format test passed")
|
|
}
|