Files

139 lines
6.3 KiB
Go

package agent
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
agentv1 "git.ipao.vip/rogee/go-sip/gen/agent/v1"
)
func TestUploadTransportFailureIsSingleAttemptAndRedactsSignedURL(t *testing.T) {
path := filepath.Join(t.TempDir(), "recording.bin")
if err := os.WriteFile(path, []byte("bytes"), 0600); err != nil {
t.Fatal(err)
}
var attempts int
client := &http.Client{Transport: uploadFailureTransport{calls: &attempts}}
grant := &agentv1.UploadGrant{UploadId: "upload-error", ObjectKey: "recording", TargetUrl: "https://oss.example.invalid/object?signature=DO_NOT_LOG", MaxBytes: 5, ExpiresAtUnixMs: time.Now().Add(time.Minute).UnixMilli()}
_, err := (UploadClient{HTTPClient: client}).UploadFile(context.Background(), grant, path)
if err == nil {
t.Fatal("transport failure hidden")
}
if strings.Contains(err.Error(), "DO_NOT_LOG") || strings.Contains(err.Error(), "signature=") {
t.Fatal("signed URL leaked in error")
}
if attempts != 1 {
t.Fatalf("upload attempted %d times", attempts)
}
if _, err := os.Stat(path); err != nil {
t.Fatal("failed upload lost its source file")
}
}
type uploadFailureTransport struct{ calls *int }
func (t uploadFailureTransport) RoundTrip(_ *http.Request) (*http.Response, error) {
*t.calls++
return nil, io.ErrUnexpectedEOF
}
func TestUploadClientUsesGrantAndVerifiesChecksum(t *testing.T) {
body := []byte("mock recording bytes")
digest := sha256.Sum256(body)
var received []byte
var receivedHeader string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
receivedHeader = r.Header.Get("x-upload-token")
received, _ = io.ReadAll(r.Body)
w.Header().Set("ETag", "etag-1")
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
assetPath := filepath.Join(t.TempDir(), "recording.bin")
if err := os.WriteFile(assetPath, body, 0o600); err != nil {
t.Fatal(err)
}
grant := &agentv1.UploadGrant{UploadId: "upload-1", TargetUrl: server.URL, ObjectKey: "recording-1", ExpiresAtUnixMs: time.Unix(101, 0).UnixMilli(), Headers: []*agentv1.Header{{Name: "x-upload-token", Value: "mock-token"}}, RequiredChecksumSha256: hex.EncodeToString(digest[:]), MaxBytes: int64(len(body))}
result, err := (UploadClient{AllowInsecureHTTP: true, Now: func() time.Time { return time.Unix(100, 0) }, AllowedHosts: map[string]struct{}{strings.TrimPrefix(server.URL, "http://"): {}}}).UploadFile(context.Background(), grant, assetPath)
if err != nil {
t.Fatal(err)
}
if result.StatusCode != http.StatusOK || result.SHA256 != grant.RequiredChecksumSha256 || result.ETag != "etag-1" || string(received) != string(body) || receivedHeader != "mock-token" {
t.Fatalf("unexpected upload result: %+v body=%q header=%q", result, received, receivedHeader)
}
if retained, statErr := os.Stat(assetPath); statErr != nil || retained.Size() != int64(len(body)) {
t.Fatalf("source asset was not retained after upload: stat=%v info=%v", statErr, retained)
}
}
func TestUploadClientFailsClosedForGrantMismatchAndHTTP(t *testing.T) {
assetPath := filepath.Join(t.TempDir(), "recording.bin")
if err := os.WriteFile(assetPath, []byte("bytes"), 0o600); err != nil {
t.Fatal(err)
}
grant := &agentv1.UploadGrant{UploadId: "upload-2", TargetUrl: "http://127.0.0.1:1/upload", ObjectKey: "recording-2", ExpiresAtUnixMs: time.Now().Add(time.Hour).UnixMilli(), RequiredChecksumSha256: strings.Repeat("a", 64), MaxBytes: 1024}
if _, err := (UploadClient{AllowInsecureHTTP: true}).UploadFile(context.Background(), grant, assetPath); !errors.Is(err, ErrUploadChecksumMismatch) {
t.Fatalf("expected typed checksum mismatch, got %v", err)
}
grant.RequiredChecksumSha256 = ""
if _, err := (UploadClient{}).UploadFile(context.Background(), grant, assetPath); !errors.Is(err, ErrUploadGrantInvalid) {
t.Fatalf("expected typed grant rejection, got %v", err)
}
}
func TestUploadClientHTTPFailureHasStatusWithoutResponseBody(t *testing.T) {
assetPath := filepath.Join(t.TempDir(), "recording.bin")
body := []byte("bytes")
if err := os.WriteFile(assetPath, body, 0o600); err != nil {
t.Fatal(err)
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusForbidden)
_, _ = w.Write([]byte("DO_NOT_LOG_SECRET"))
}))
defer server.Close()
sum := sha256.Sum256(body)
grant := &agentv1.UploadGrant{UploadId: "upload-forbidden", TargetUrl: server.URL, ObjectKey: "recording-forbidden", ExpiresAtUnixMs: time.Now().Add(time.Minute).UnixMilli(), RequiredChecksumSha256: hex.EncodeToString(sum[:]), MaxBytes: 1024}
_, err := (UploadClient{AllowInsecureHTTP: true}).UploadFile(context.Background(), grant, assetPath)
var response *UploadHTTPError
if !errors.As(err, &response) || response.StatusCode != http.StatusForbidden || strings.Contains(err.Error(), "DO_NOT_LOG_SECRET") {
t.Fatalf("explicit PUT rejection was not typed and redacted: %v", err)
}
}
func TestUploadClientEnforcesSizeAndHost(t *testing.T) {
assetPath := filepath.Join(t.TempDir(), "recording.bin")
if err := os.WriteFile(assetPath, []byte("bytes"), 0o600); err != nil {
t.Fatal(err)
}
grant := &agentv1.UploadGrant{UploadId: "upload-3", TargetUrl: "https://oss.example.invalid/upload", ObjectKey: "recording-3", ExpiresAtUnixMs: time.Now().Add(time.Hour).UnixMilli(), MaxBytes: 1}
if _, err := (UploadClient{}).UploadFile(context.Background(), grant, assetPath); err == nil {
t.Fatal("expected size rejection")
}
grant.MaxBytes = 1024
if _, err := (UploadClient{AllowedHosts: map[string]struct{}{"other.example.invalid": {}}}).UploadFile(context.Background(), grant, assetPath); err == nil {
t.Fatal("expected host rejection")
}
}
func TestUploadClientRejectsExpiredGrant(t *testing.T) {
assetPath := filepath.Join(t.TempDir(), "recording.bin")
if err := os.WriteFile(assetPath, []byte("bytes"), 0o600); err != nil {
t.Fatal(err)
}
grant := &agentv1.UploadGrant{UploadId: "upload-expired", TargetUrl: "https://oss.example.invalid/upload", ObjectKey: "recording-expired", ExpiresAtUnixMs: time.Unix(100, 0).UnixMilli(), MaxBytes: 1024}
if _, err := (UploadClient{Now: func() time.Time { return time.Unix(100, 0) }}).UploadFile(context.Background(), grant, assetPath); !errors.Is(err, ErrUploadGrantExpired) {
t.Fatalf("expected typed expired grant rejection, got %v", err)
}
}