feat: store layer with indexes, optimistic concurrency, GridFS, credential crypto (phase 2)
- mongo-driver v2 connection with bounded startup retry + /readyz gate - idempotent creation of all §4.9 indexes - UpdateVersioned: version-filtered updates, conflict vs not-found errors - AES-256-GCM Seal/Open for ticketing credentials, base64(nonce|ct) - GridFS file store: MIME sniffing, MAX_UPLOAD_MB cap, sha256 metadata - HMAC-signed short-lived file URL tokens (§5.1, 1h TTL) - integration tests against compose Mongo via docker-compose.test.yml overlay Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSignVerifyRoundTrip(t *testing.T) {
|
||||
secret := []byte("0123456789abcdef0123456789abcdef")
|
||||
now := time.Now()
|
||||
token := SignToken(secret, "01JFILE", now.Add(time.Hour))
|
||||
|
||||
if !VerifyToken(secret, "01JFILE", token, now) {
|
||||
t.Fatal("fresh token must verify")
|
||||
}
|
||||
if !VerifyToken(secret, "01JFILE", token, now.Add(59*time.Minute)) {
|
||||
t.Fatal("token must verify just before expiry")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyRejections(t *testing.T) {
|
||||
secret := []byte("0123456789abcdef0123456789abcdef")
|
||||
now := time.Now()
|
||||
token := SignToken(secret, "01JFILE", now.Add(time.Hour))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fileID string
|
||||
token string
|
||||
at time.Time
|
||||
}{
|
||||
{"expired", "01JFILE", token, now.Add(2 * time.Hour)},
|
||||
{"wrong file", "01JOTHER", token, now},
|
||||
{"garbage token", "01JFILE", "not-a-token", now},
|
||||
{"empty token", "01JFILE", "", now},
|
||||
{"truncated token", "01JFILE", token[:len(token)-10], now},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if VerifyToken(secret, tt.fileID, tt.token, tt.at) {
|
||||
t.Error("must not verify")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if VerifyToken([]byte("another-secret-another-secret-xx"), "01JFILE", token, now) {
|
||||
t.Error("different secret must not verify")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignedURLShape(t *testing.T) {
|
||||
secret := []byte("0123456789abcdef0123456789abcdef")
|
||||
u := SignedURL("http://localhost:8787", secret, "01JFILE", time.Now().Add(time.Hour))
|
||||
if !strings.HasPrefix(u, "http://localhost:8787/files/01JFILE?st=") {
|
||||
t.Fatalf("unexpected URL %q", u)
|
||||
}
|
||||
token := u[strings.Index(u, "?st=")+4:]
|
||||
if !VerifyToken(secret, "01JFILE", token, time.Now()) {
|
||||
t.Fatal("token embedded in URL must verify")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user