mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-19 10:52:04 +00:00
Improved unit testing and integration tests
This commit is contained in:
@@ -14,7 +14,7 @@ go_run:
|
||||
|
||||
test:
|
||||
@echo "\n....Running tests for $(GO_PROJECT_NAME)...."
|
||||
LOG_IGNORE=1 go test -v ./...
|
||||
LOG_IGNORE=1 go test -v -timeout 300s ./...
|
||||
|
||||
bench:
|
||||
@echo "\n....Running benchmarks for $(GO_PROJECT_NAME)...."
|
||||
|
||||
@@ -0,0 +1,681 @@
|
||||
package predastore
|
||||
|
||||
// Integration tests for predastore service
|
||||
//
|
||||
// These tests start a real predastore daemon and test S3 operations against it.
|
||||
// Tests use AWS SDK Go v1 to verify bucket operations, authentication, and file operations.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Test credentials from config
|
||||
const (
|
||||
validAccessKey = "AKIAIOSFODNN7EXAMPLE"
|
||||
validSecretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
||||
invalidAccessKey = "INVALIDACCESSKEY"
|
||||
invalidSecretKey = "InvalidSecretKey123"
|
||||
testBucket = "test-bucket"
|
||||
publicBucket = "public-bucket"
|
||||
testRegion = "us-east-1"
|
||||
testPort = 18443
|
||||
testHost = "127.0.0.1"
|
||||
)
|
||||
|
||||
// Global server instance for integration tests
|
||||
var (
|
||||
serverStarted bool
|
||||
serverStartMutex sync.Mutex
|
||||
sharedTestDir string
|
||||
sharedConfig *Config
|
||||
)
|
||||
|
||||
// generateTestCertificate generates a self-signed TLS certificate for testing
|
||||
func generateTestCertificate(certPath, keyPath string) error {
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(24 * time.Hour)
|
||||
|
||||
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Predastore Test"},
|
||||
CommonName: "localhost",
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
||||
DNSNames: []string{"localhost"},
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certOut, err := os.Create(certPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer certOut.Close()
|
||||
|
||||
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyOut, err := os.Create(keyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer keyOut.Close()
|
||||
|
||||
privBytes, err := x509.MarshalECPrivateKey(priv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes})
|
||||
}
|
||||
|
||||
// startPredastoreServer starts the predastore server once for all tests
|
||||
func startPredastoreServer(t *testing.T) *Config {
|
||||
serverStartMutex.Lock()
|
||||
defer serverStartMutex.Unlock()
|
||||
|
||||
if serverStarted {
|
||||
// Server already running, return shared config
|
||||
return sharedConfig
|
||||
}
|
||||
|
||||
// Create persistent test environment (not using t.TempDir() for shared server)
|
||||
testDir, err := os.MkdirTemp("", "predastore-integration-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
sharedTestDir = testDir
|
||||
|
||||
// Create bucket directories
|
||||
testBucketDir := filepath.Join(testDir, "test-bucket")
|
||||
publicBucketDir := filepath.Join(testDir, "public-bucket")
|
||||
|
||||
err = os.MkdirAll(testBucketDir, 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test-bucket dir: %v", err)
|
||||
}
|
||||
err = os.MkdirAll(publicBucketDir, 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create public-bucket dir: %v", err)
|
||||
}
|
||||
|
||||
// Create TLS cert directory
|
||||
certDir := filepath.Join(testDir, "certs")
|
||||
err = os.MkdirAll(certDir, 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create cert dir: %v", err)
|
||||
}
|
||||
|
||||
certPath := filepath.Join(certDir, "test.crt")
|
||||
keyPath := filepath.Join(certDir, "test.key")
|
||||
|
||||
err = generateTestCertificate(certPath, keyPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate certificate: %v", err)
|
||||
}
|
||||
|
||||
// Create config file
|
||||
configPath := filepath.Join(testDir, "predastore_test.toml")
|
||||
configContent := `version = "1.0"
|
||||
region = "us-east-1"
|
||||
host = "127.0.0.1"
|
||||
port = 18443
|
||||
debug = true
|
||||
disable_logging = false
|
||||
base_path = "` + testDir + `"
|
||||
|
||||
[[buckets]]
|
||||
name = "test-bucket"
|
||||
region = "us-east-1"
|
||||
type = "fs"
|
||||
pathname = "` + testDir + `test-bucket"
|
||||
public = false
|
||||
encryption = ""
|
||||
|
||||
[[buckets]]
|
||||
name = "public-bucket"
|
||||
region = "us-east-1"
|
||||
type = "fs"
|
||||
pathname = "` + testDir + `public-bucket"
|
||||
public = true
|
||||
encryption = ""
|
||||
|
||||
[[auth]]
|
||||
access_key_id = "AKIAIOSFODNN7EXAMPLE"
|
||||
secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
||||
policy = [
|
||||
{ bucket = "test-bucket", actions = ["s3:ListBucket", "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListAllMyBuckets"] },
|
||||
{ bucket = "public-bucket", actions = ["s3:ListBucket", "s3:GetObject", "s3:PutObject", "s3:DeleteObject"] },
|
||||
]
|
||||
`
|
||||
|
||||
err = os.WriteFile(configPath, []byte(configContent), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write config: %v", err)
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
ConfigPath: configPath,
|
||||
Port: 18443,
|
||||
Host: "127.0.0.1",
|
||||
Debug: true,
|
||||
BasePath: testDir,
|
||||
TlsCert: certPath,
|
||||
TlsKey: keyPath,
|
||||
}
|
||||
sharedConfig = cfg
|
||||
|
||||
// Start server in background
|
||||
go func() {
|
||||
svc, _ := New(cfg)
|
||||
svc.Start()
|
||||
}()
|
||||
|
||||
// Wait for server to be ready
|
||||
if !waitForServer(10 * time.Second) {
|
||||
t.Fatal("Failed to start predastore server")
|
||||
}
|
||||
|
||||
serverStarted = true
|
||||
t.Logf("Predastore server started successfully")
|
||||
t.Logf("Test directory: %s", testDir)
|
||||
t.Logf("Bucket directories created: %s, %s", testBucketDir, publicBucketDir)
|
||||
|
||||
// Verify directories exist
|
||||
if _, err := os.Stat(testBucketDir); os.IsNotExist(err) {
|
||||
t.Fatalf("test-bucket directory does not exist: %s", testBucketDir)
|
||||
}
|
||||
if _, err := os.Stat(publicBucketDir); os.IsNotExist(err) {
|
||||
t.Fatalf("public-bucket directory does not exist: %s", publicBucketDir)
|
||||
}
|
||||
t.Logf("Verified bucket directories exist")
|
||||
|
||||
// NOTE: Cleanup will happen automatically when test process exits
|
||||
// Don't use t.Cleanup() as it runs after each test, not at the end
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
// waitForServer waits for the predastore server to be ready
|
||||
func waitForServer(timeout time.Duration) bool {
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
resp, err := client.Get(fmt.Sprintf("https://%s:%d/", testHost, testPort))
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
// Give a bit more time for config to load
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
return true
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// createS3Client creates an AWS S3 client for testing
|
||||
func createS3Client(accessKey, secretKey string) *s3.S3 {
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
httpClient := &http.Client{Transport: tr}
|
||||
|
||||
sess := session.Must(session.NewSession(&aws.Config{
|
||||
Region: aws.String(testRegion),
|
||||
Endpoint: aws.String(fmt.Sprintf("https://%s:%d", testHost, testPort)),
|
||||
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
|
||||
S3ForcePathStyle: aws.Bool(true),
|
||||
DisableSSL: aws.Bool(false),
|
||||
HTTPClient: httpClient,
|
||||
}))
|
||||
|
||||
return s3.New(sess)
|
||||
}
|
||||
|
||||
// TestIntegration_BucketListing tests that both buckets from config exist
|
||||
func TestIntegration_BucketListing(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
// Start server (only starts once)
|
||||
startPredastoreServer(t)
|
||||
|
||||
// Create S3 client
|
||||
client := createS3Client(validAccessKey, validSecretKey)
|
||||
|
||||
// List all buckets
|
||||
result, err := client.ListBuckets(&s3.ListBucketsInput{})
|
||||
assert.NoError(t, err, "ListBuckets should not return error")
|
||||
assert.NotNil(t, result, "ListBuckets result should not be nil")
|
||||
|
||||
// Verify both buckets exist
|
||||
buckets := make(map[string]bool)
|
||||
for _, bucket := range result.Buckets {
|
||||
buckets[*bucket.Name] = true
|
||||
t.Logf("Found bucket: %s", *bucket.Name)
|
||||
}
|
||||
|
||||
//assert.True(t, buckets[testBucket], "test-bucket should exist")
|
||||
//assert.True(t, buckets[publicBucket], "public-bucket should exist")
|
||||
assert.Len(t, buckets, 2, "Should have exactly 2 buckets")
|
||||
}
|
||||
|
||||
// TestIntegration_Authentication_Valid tests authentication with valid credentials
|
||||
func TestIntegration_Authentication_Valid(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
startPredastoreServer(t)
|
||||
|
||||
// Create client with valid credentials
|
||||
client := createS3Client(validAccessKey, validSecretKey)
|
||||
|
||||
// Try to list buckets
|
||||
result, err := client.ListBuckets(&s3.ListBucketsInput{})
|
||||
assert.NoError(t, err, "Valid credentials should authenticate successfully")
|
||||
if assert.NotNil(t, result, "Result should not be nil") {
|
||||
// Just verify authentication worked - bucket count is verified in BucketListing test
|
||||
t.Logf("Authentication successful, found %d buckets", len(result.Buckets))
|
||||
}
|
||||
}
|
||||
|
||||
// TestIntegration_Authentication_Invalid tests authentication with invalid credentials
|
||||
func TestIntegration_Authentication_Invalid(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
startPredastoreServer(t)
|
||||
|
||||
// Create client with invalid credentials
|
||||
client := createS3Client(invalidAccessKey, invalidSecretKey)
|
||||
|
||||
// Try to list buckets - should fail
|
||||
_, err := client.ListBuckets(&s3.ListBucketsInput{})
|
||||
assert.Error(t, err, "Invalid credentials should fail authentication")
|
||||
|
||||
// Verify error contains authentication-related message
|
||||
errStr := err.Error()
|
||||
assert.True(t,
|
||||
strings.Contains(errStr, "InvalidAccessKeyId") ||
|
||||
strings.Contains(errStr, "SignatureDoesNotMatch") ||
|
||||
strings.Contains(errStr, "Forbidden") ||
|
||||
strings.Contains(errStr, "403"),
|
||||
"Error should indicate authentication failure, got: %s", errStr)
|
||||
}
|
||||
|
||||
// TestIntegration_FileUpload_TestBucket tests file upload to test-bucket
|
||||
func TestIntegration_FileUpload_TestBucket(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
startPredastoreServer(t)
|
||||
|
||||
client := createS3Client(validAccessKey, validSecretKey)
|
||||
|
||||
// Upload test file
|
||||
key := "test1.txt"
|
||||
expectedContent := "hello-world-test-1-" + testBucket
|
||||
body := strings.NewReader(expectedContent)
|
||||
|
||||
_, err := client.PutObject(&s3.PutObjectInput{
|
||||
Bucket: aws.String(testBucket),
|
||||
Key: aws.String(key),
|
||||
Body: body,
|
||||
})
|
||||
assert.NoError(t, err, "File upload should succeed")
|
||||
|
||||
// Download and verify content
|
||||
result, err := client.GetObject(&s3.GetObjectInput{
|
||||
Bucket: aws.String(testBucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
if !assert.NoError(t, err, "File download should succeed") {
|
||||
return // Skip rest if download failed
|
||||
}
|
||||
if !assert.NotNil(t, result, "Result should not be nil") {
|
||||
return
|
||||
}
|
||||
|
||||
defer result.Body.Close()
|
||||
downloadedContent, err := io.ReadAll(result.Body)
|
||||
assert.NoError(t, err, "Reading file content should succeed")
|
||||
assert.Equal(t, expectedContent, string(downloadedContent), "File content should match")
|
||||
|
||||
t.Logf("Successfully uploaded and verified file: s3://%s/%s", testBucket, key)
|
||||
}
|
||||
|
||||
// TestIntegration_FileUpload_PublicBucket tests file upload to public-bucket
|
||||
func TestIntegration_FileUpload_PublicBucket(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
startPredastoreServer(t)
|
||||
|
||||
client := createS3Client(validAccessKey, validSecretKey)
|
||||
|
||||
// Upload test file
|
||||
key := "test1.txt"
|
||||
expectedContent := "hello-world-test-1-" + publicBucket
|
||||
body := strings.NewReader(expectedContent)
|
||||
|
||||
_, err := client.PutObject(&s3.PutObjectInput{
|
||||
Bucket: aws.String(publicBucket),
|
||||
Key: aws.String(key),
|
||||
Body: body,
|
||||
})
|
||||
assert.NoError(t, err, "File upload should succeed")
|
||||
|
||||
// Download and verify content
|
||||
result, err := client.GetObject(&s3.GetObjectInput{
|
||||
Bucket: aws.String(publicBucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
if !assert.NoError(t, err, "File download should succeed") {
|
||||
return
|
||||
}
|
||||
if !assert.NotNil(t, result, "Result should not be nil") {
|
||||
return
|
||||
}
|
||||
|
||||
defer result.Body.Close()
|
||||
downloadedContent, err := io.ReadAll(result.Body)
|
||||
assert.NoError(t, err, "Reading file content should succeed")
|
||||
assert.Equal(t, expectedContent, string(downloadedContent), "File content should match")
|
||||
|
||||
t.Logf("Successfully uploaded and verified file: s3://%s/%s", publicBucket, key)
|
||||
}
|
||||
|
||||
func TestIntegration_FileUpload_PublicBucket2(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
startPredastoreServer(t)
|
||||
|
||||
client := createS3Client(validAccessKey, validSecretKey)
|
||||
|
||||
time.Sleep(20 * time.Second)
|
||||
|
||||
// Upload test file
|
||||
key := "test1.txt"
|
||||
expectedContent := "hello-world-test-1-" + publicBucket
|
||||
body := strings.NewReader(expectedContent)
|
||||
|
||||
_, err := client.PutObject(&s3.PutObjectInput{
|
||||
Bucket: aws.String(publicBucket),
|
||||
Key: aws.String(key),
|
||||
Body: body,
|
||||
})
|
||||
assert.NoError(t, err, "File upload should succeed")
|
||||
|
||||
// Download and verify content
|
||||
result, err := client.GetObject(&s3.GetObjectInput{
|
||||
Bucket: aws.String(publicBucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
if !assert.NoError(t, err, "File download should succeed") {
|
||||
return
|
||||
}
|
||||
if !assert.NotNil(t, result, "Result should not be nil") {
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(30 * time.Second)
|
||||
|
||||
defer result.Body.Close()
|
||||
downloadedContent, err := io.ReadAll(result.Body)
|
||||
assert.NoError(t, err, "Reading file content should succeed")
|
||||
assert.Equal(t, expectedContent, string(downloadedContent), "File content should match")
|
||||
|
||||
t.Logf("Successfully uploaded and verified file: s3://%s/%s", publicBucket, key)
|
||||
}
|
||||
|
||||
// TestIntegration_FileOperations_Complete tests full file lifecycle
|
||||
func TestIntegration_FileOperations_Complete(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
startPredastoreServer(t)
|
||||
|
||||
client := createS3Client(validAccessKey, validSecretKey)
|
||||
|
||||
// Test with both buckets
|
||||
buckets := []string{testBucket, publicBucket}
|
||||
|
||||
for _, bucket := range buckets {
|
||||
t.Run(bucket, func(t *testing.T) {
|
||||
key := "lifecycle-test.txt"
|
||||
content := fmt.Sprintf("hello-world-test-1-%s", bucket)
|
||||
|
||||
// 1. Upload file
|
||||
_, err := client.PutObject(&s3.PutObjectInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
Body: strings.NewReader(content),
|
||||
})
|
||||
assert.NoError(t, err, "Upload should succeed")
|
||||
|
||||
// 2. Verify file exists in listing
|
||||
listResult, err := client.ListObjectsV2(&s3.ListObjectsV2Input{
|
||||
Bucket: aws.String(bucket),
|
||||
})
|
||||
assert.NoError(t, err, "List should succeed")
|
||||
|
||||
found := false
|
||||
for _, obj := range listResult.Contents {
|
||||
if *obj.Key == key {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "File should appear in bucket listing")
|
||||
|
||||
// 3. Download and verify content
|
||||
getResult, err := client.GetObject(&s3.GetObjectInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
if !assert.NoError(t, err, "Download should succeed") {
|
||||
return
|
||||
}
|
||||
if !assert.NotNil(t, getResult, "Get result should not be nil") {
|
||||
return
|
||||
}
|
||||
|
||||
downloadedContent, err := io.ReadAll(getResult.Body)
|
||||
getResult.Body.Close()
|
||||
assert.NoError(t, err, "Reading content should succeed")
|
||||
assert.Equal(t, content, string(downloadedContent), "Content should match")
|
||||
|
||||
// 4. Delete file
|
||||
_, err = client.DeleteObject(&s3.DeleteObjectInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
assert.NoError(t, err, "Delete should succeed")
|
||||
|
||||
// 5. Verify file no longer exists
|
||||
listResult2, err := client.ListObjectsV2(&s3.ListObjectsV2Input{
|
||||
Bucket: aws.String(bucket),
|
||||
})
|
||||
assert.NoError(t, err, "List after delete should succeed")
|
||||
|
||||
found = false
|
||||
for _, obj := range listResult2.Contents {
|
||||
if *obj.Key == key {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.False(t, found, "File should not appear in listing after delete")
|
||||
|
||||
// 6. Verify GET returns error for deleted file
|
||||
_, err = client.GetObject(&s3.GetObjectInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
assert.Error(t, err, "GET on deleted file should return error")
|
||||
|
||||
t.Logf("Complete lifecycle test passed for bucket: %s", bucket)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestIntegration_MultipleFiles tests uploading multiple files
|
||||
func TestIntegration_MultipleFiles(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
startPredastoreServer(t)
|
||||
|
||||
client := createS3Client(validAccessKey, validSecretKey)
|
||||
|
||||
// Upload multiple files to test-bucket
|
||||
fileCount := 5
|
||||
keys := make([]string, fileCount)
|
||||
|
||||
for i := 0; i < fileCount; i++ {
|
||||
keys[i] = fmt.Sprintf("multi-test-%d.txt", i)
|
||||
content := fmt.Sprintf("content-file-%d", i)
|
||||
|
||||
_, err := client.PutObject(&s3.PutObjectInput{
|
||||
Bucket: aws.String(testBucket),
|
||||
Key: aws.String(keys[i]),
|
||||
Body: strings.NewReader(content),
|
||||
})
|
||||
assert.NoError(t, err, "Upload file %d should succeed", i)
|
||||
}
|
||||
|
||||
// List and verify all files exist
|
||||
listResult, err := client.ListObjectsV2(&s3.ListObjectsV2Input{
|
||||
Bucket: aws.String(testBucket),
|
||||
})
|
||||
assert.NoError(t, err, "List should succeed")
|
||||
|
||||
foundCount := 0
|
||||
for _, obj := range listResult.Contents {
|
||||
for _, key := range keys {
|
||||
if *obj.Key == key {
|
||||
foundCount++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.Equal(t, fileCount, foundCount, "All uploaded files should be listed")
|
||||
|
||||
// Cleanup - delete all test files
|
||||
for _, key := range keys {
|
||||
client.DeleteObject(&s3.DeleteObjectInput{
|
||||
Bucket: aws.String(testBucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestIntegration_LargeFile tests uploading a larger file (1MB)
|
||||
func TestIntegration_LargeFile(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
startPredastoreServer(t)
|
||||
|
||||
client := createS3Client(validAccessKey, validSecretKey)
|
||||
|
||||
// Create 1MB file
|
||||
size := 1024 * 1024
|
||||
largeData := bytes.Repeat([]byte("A"), size)
|
||||
|
||||
key := "large-file-test.bin"
|
||||
|
||||
// Upload
|
||||
_, err := client.PutObject(&s3.PutObjectInput{
|
||||
Bucket: aws.String(testBucket),
|
||||
Key: aws.String(key),
|
||||
Body: bytes.NewReader(largeData),
|
||||
})
|
||||
assert.NoError(t, err, "Large file upload should succeed")
|
||||
|
||||
// Download and verify
|
||||
result, err := client.GetObject(&s3.GetObjectInput{
|
||||
Bucket: aws.String(testBucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
if !assert.NoError(t, err, "Large file download should succeed") {
|
||||
return
|
||||
}
|
||||
if !assert.NotNil(t, result, "Result should not be nil") {
|
||||
return
|
||||
}
|
||||
|
||||
downloadedData, err := io.ReadAll(result.Body)
|
||||
result.Body.Close()
|
||||
assert.NoError(t, err, "Reading large file should succeed")
|
||||
assert.Equal(t, size, len(downloadedData), "Downloaded size should match")
|
||||
assert.Equal(t, largeData, downloadedData, "Downloaded content should match")
|
||||
|
||||
// Cleanup
|
||||
client.DeleteObject(&s3.DeleteObjectInput{
|
||||
Bucket: aws.String(testBucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
|
||||
t.Logf("Successfully uploaded and verified 1MB file")
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
package predastore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestNew tests the service constructor
|
||||
func TestNew(t *testing.T) {
|
||||
cfg := &Config{
|
||||
ConfigPath: "/tmp/test-config.toml",
|
||||
Port: 8443,
|
||||
Host: "0.0.0.0",
|
||||
Debug: false,
|
||||
BasePath: "/tmp/predastore",
|
||||
TlsCert: "/tmp/cert.pem",
|
||||
TlsKey: "/tmp/key.pem",
|
||||
}
|
||||
|
||||
svc, err := New(cfg)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, svc)
|
||||
assert.NotNil(t, svc.Config)
|
||||
assert.Equal(t, "/tmp/test-config.toml", svc.Config.ConfigPath)
|
||||
assert.Equal(t, 8443, svc.Config.Port)
|
||||
assert.Equal(t, "0.0.0.0", svc.Config.Host)
|
||||
assert.False(t, svc.Config.Debug)
|
||||
assert.Equal(t, "/tmp/predastore", svc.Config.BasePath)
|
||||
assert.Equal(t, "/tmp/cert.pem", svc.Config.TlsCert)
|
||||
assert.Equal(t, "/tmp/key.pem", svc.Config.TlsKey)
|
||||
}
|
||||
|
||||
// TestNewWithNilConfig tests that New handles nil config correctly
|
||||
func TestNewWithNilConfig(t *testing.T) {
|
||||
cfg := &Config{}
|
||||
svc, err := New(cfg)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, svc)
|
||||
assert.NotNil(t, svc.Config)
|
||||
}
|
||||
|
||||
// TestConfigDefaults tests Config struct default values
|
||||
func TestConfigDefaults(t *testing.T) {
|
||||
cfg := &Config{}
|
||||
|
||||
assert.Empty(t, cfg.ConfigPath)
|
||||
assert.Empty(t, cfg.Host)
|
||||
assert.Equal(t, 0, cfg.Port)
|
||||
assert.False(t, cfg.Debug)
|
||||
assert.Empty(t, cfg.BasePath)
|
||||
assert.Empty(t, cfg.TlsCert)
|
||||
assert.Empty(t, cfg.TlsKey)
|
||||
}
|
||||
|
||||
// TestConfigWithDebug tests Config with debug enabled
|
||||
func TestConfigWithDebug(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Debug: true,
|
||||
ConfigPath: "/etc/predastore/config.toml",
|
||||
Port: 8443,
|
||||
}
|
||||
|
||||
assert.True(t, cfg.Debug)
|
||||
assert.Equal(t, "/etc/predastore/config.toml", cfg.ConfigPath)
|
||||
assert.Equal(t, 8443, cfg.Port)
|
||||
}
|
||||
|
||||
// TestConfigFullyPopulated tests a fully populated config
|
||||
func TestConfigFullyPopulated(t *testing.T) {
|
||||
cfg := &Config{
|
||||
ConfigPath: "/etc/predastore/config.toml",
|
||||
Port: 9443,
|
||||
Host: "127.0.0.1",
|
||||
Debug: true,
|
||||
BasePath: "/var/lib/predastore",
|
||||
TlsCert: "/etc/ssl/certs/predastore.crt",
|
||||
TlsKey: "/etc/ssl/private/predastore.key",
|
||||
}
|
||||
|
||||
assert.Equal(t, "/etc/predastore/config.toml", cfg.ConfigPath)
|
||||
assert.Equal(t, 9443, cfg.Port)
|
||||
assert.Equal(t, "127.0.0.1", cfg.Host)
|
||||
assert.True(t, cfg.Debug)
|
||||
assert.Equal(t, "/var/lib/predastore", cfg.BasePath)
|
||||
assert.Equal(t, "/etc/ssl/certs/predastore.crt", cfg.TlsCert)
|
||||
assert.Equal(t, "/etc/ssl/private/predastore.key", cfg.TlsKey)
|
||||
}
|
||||
|
||||
// TestServiceMethods tests the service interface methods
|
||||
func TestServiceMethods(t *testing.T) {
|
||||
cfg := &Config{
|
||||
ConfigPath: "/tmp/test-config.toml",
|
||||
Port: 8443,
|
||||
Host: "127.0.0.1",
|
||||
}
|
||||
|
||||
svc, err := New(cfg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test Status - returns empty string and no error
|
||||
status, err := svc.Status()
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, status)
|
||||
|
||||
// Test Reload - returns no error
|
||||
err = svc.Reload()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestServiceShutdown tests that Shutdown calls Stop. Can't test, given it will exit the process and tests will fail.
|
||||
/*
|
||||
func TestServiceShutdown(t *testing.T) {
|
||||
cfg := &Config{
|
||||
ConfigPath: "/tmp/test-config.toml",
|
||||
Port: 8443,
|
||||
}
|
||||
|
||||
svc, err := New(cfg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Shutdown should call Stop internally
|
||||
// Since we can't actually start the service, we just verify it doesn't panic
|
||||
err = svc.Shutdown()
|
||||
// Will error because no PID file exists, which is expected
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// TestServiceStop tests the Stop method
|
||||
|
||||
func TestServiceStop(t *testing.T) {
|
||||
cfg := &Config{
|
||||
ConfigPath: "/tmp/test-config.toml",
|
||||
Port: 8443,
|
||||
}
|
||||
|
||||
svc, err := New(cfg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Stop should try to read PID file and stop the process
|
||||
err = svc.Stop()
|
||||
// Will error because no PID file exists, which is expected in tests
|
||||
assert.Error(t, err)
|
||||
}
|
||||
*/
|
||||
|
||||
// TestServiceNameConstant tests the serviceName constant
|
||||
func TestServiceNameConstant(t *testing.T) {
|
||||
assert.Equal(t, "predastore", serviceName)
|
||||
}
|
||||
|
||||
// TestConfigPortRange tests port validation
|
||||
func TestConfigPortRange(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
port int
|
||||
valid bool
|
||||
expected int
|
||||
}{
|
||||
{"Standard HTTPS", 443, true, 443},
|
||||
{"Custom Port", 8443, true, 8443},
|
||||
{"High Port", 65535, true, 65535},
|
||||
{"Port 80", 80, true, 80},
|
||||
{"Port 8080", 8080, true, 8080},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Port: tc.port,
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.expected, cfg.Port)
|
||||
if tc.valid {
|
||||
assert.True(t, cfg.Port > 0)
|
||||
assert.True(t, cfg.Port <= 65535)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigHostnames tests host configuration
|
||||
func TestConfigHostnames(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
host string
|
||||
expected string
|
||||
}{
|
||||
{"Localhost", "127.0.0.1", "127.0.0.1"},
|
||||
{"All Interfaces", "0.0.0.0", "0.0.0.0"},
|
||||
{"Localhost Name", "localhost", "localhost"},
|
||||
{"Custom Domain", "s3.example.com", "s3.example.com"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Host: tc.host,
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.expected, cfg.Host)
|
||||
assert.NotEmpty(t, cfg.Host)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigBasePath tests base path configuration
|
||||
func TestConfigBasePath(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
basePath string
|
||||
}{
|
||||
{"Absolute Path", "/var/lib/predastore"},
|
||||
{"Relative Path", "data/predastore"},
|
||||
{"Temp Directory", "/tmp/predastore"},
|
||||
{"User Directory", "/home/user/predastore"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cfg := &Config{
|
||||
BasePath: tc.basePath,
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.basePath, cfg.BasePath)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigTLSPaths tests TLS certificate and key paths
|
||||
func TestConfigTLSPaths(t *testing.T) {
|
||||
cfg := &Config{
|
||||
TlsCert: "/etc/ssl/certs/server.crt",
|
||||
TlsKey: "/etc/ssl/private/server.key",
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, cfg.TlsCert)
|
||||
assert.NotEmpty(t, cfg.TlsKey)
|
||||
assert.Contains(t, cfg.TlsCert, ".crt")
|
||||
assert.Contains(t, cfg.TlsKey, ".key")
|
||||
}
|
||||
|
||||
// TestConfigMinimal tests minimal required config
|
||||
func TestConfigMinimal(t *testing.T) {
|
||||
cfg := &Config{
|
||||
ConfigPath: "/tmp/config.toml",
|
||||
Port: 8443,
|
||||
Host: "127.0.0.1",
|
||||
}
|
||||
|
||||
svc, err := New(cfg)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, svc)
|
||||
|
||||
// Minimal config should work
|
||||
assert.NotEmpty(t, cfg.ConfigPath)
|
||||
assert.True(t, cfg.Port > 0)
|
||||
assert.NotEmpty(t, cfg.Host)
|
||||
}
|
||||
|
||||
// TestServiceStartWithoutConfig tests Start method behavior
|
||||
// Note: This will fail without proper config file, which is expected
|
||||
func TestServiceStartWithoutConfig(t *testing.T) {
|
||||
// Skip this test - it requires actual config file and will block/exit
|
||||
// This is covered by integration tests instead
|
||||
t.Skip("Skipping test that requires actual predastore config file - covered by integration tests")
|
||||
}
|
||||
|
||||
// TestConfigDebugFlag tests debug flag behavior
|
||||
func TestConfigDebugFlag(t *testing.T) {
|
||||
// Debug enabled
|
||||
cfg1 := &Config{
|
||||
Debug: true,
|
||||
}
|
||||
assert.True(t, cfg1.Debug)
|
||||
|
||||
// Debug disabled
|
||||
cfg2 := &Config{
|
||||
Debug: false,
|
||||
}
|
||||
assert.False(t, cfg2.Debug)
|
||||
|
||||
// Default should be false
|
||||
cfg3 := &Config{}
|
||||
assert.False(t, cfg3.Debug)
|
||||
}
|
||||
|
||||
// TestConfigValidation tests validation of required fields
|
||||
func TestConfigValidation(t *testing.T) {
|
||||
// Empty config - fields should be empty/zero
|
||||
cfg := &Config{}
|
||||
assert.Empty(t, cfg.ConfigPath)
|
||||
assert.Equal(t, 0, cfg.Port)
|
||||
assert.Empty(t, cfg.Host)
|
||||
|
||||
// Valid config
|
||||
cfg2 := &Config{
|
||||
ConfigPath: "/etc/predastore/config.toml",
|
||||
Port: 8443,
|
||||
Host: "0.0.0.0",
|
||||
}
|
||||
assert.NotEmpty(t, cfg2.ConfigPath)
|
||||
assert.True(t, cfg2.Port > 0)
|
||||
assert.NotEmpty(t, cfg2.Host)
|
||||
}
|
||||
|
||||
// TestMultipleServiceInstances tests creating multiple service instances
|
||||
func TestMultipleServiceInstances(t *testing.T) {
|
||||
cfg1 := &Config{
|
||||
ConfigPath: "/tmp/config1.toml",
|
||||
Port: 8443,
|
||||
Host: "127.0.0.1",
|
||||
}
|
||||
|
||||
cfg2 := &Config{
|
||||
ConfigPath: "/tmp/config2.toml",
|
||||
Port: 9443,
|
||||
Host: "127.0.0.1",
|
||||
}
|
||||
|
||||
svc1, err := New(cfg1)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, svc1)
|
||||
|
||||
svc2, err := New(cfg2)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, svc2)
|
||||
|
||||
// Services should be independent
|
||||
assert.NotEqual(t, svc1.Config.Port, svc2.Config.Port)
|
||||
assert.NotEqual(t, svc1.Config.ConfigPath, svc2.Config.ConfigPath)
|
||||
}
|
||||
|
||||
// TestConfigPointerPreservation tests that config pointer is preserved
|
||||
func TestConfigPointerPreservation(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Port: 8443,
|
||||
Host: "127.0.0.1",
|
||||
}
|
||||
|
||||
svc, err := New(cfg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Modify original config
|
||||
cfg.Port = 9443
|
||||
|
||||
// Service should reflect the change (same pointer)
|
||||
assert.Equal(t, 9443, svc.Config.Port)
|
||||
}
|
||||
@@ -46,15 +46,15 @@ func setupTestConfig(t *testing.T, natsURL string) *Config {
|
||||
testDir := t.TempDir()
|
||||
|
||||
cfg := &Config{
|
||||
NatsHost: natsURL,
|
||||
S3Host: "https://s3.mock.local",
|
||||
Bucket: "test-bucket",
|
||||
Region: "us-east-1",
|
||||
AccessKey: "test-access-key",
|
||||
SecretKey: "test-secret-key",
|
||||
BaseDir: testDir,
|
||||
PluginPath: filepath.Join(testDir, "plugins"), // Use temp dir to avoid actual nbdkit execution
|
||||
Debug: true,
|
||||
NatsHost: natsURL,
|
||||
S3Host: "https://s3.mock.local",
|
||||
Bucket: "test-bucket",
|
||||
Region: "us-east-1",
|
||||
AccessKey: "test-access-key",
|
||||
SecretKey: "test-secret-key",
|
||||
BaseDir: testDir,
|
||||
PluginPath: filepath.Join(testDir, "plugins"), // Use temp dir to avoid actual nbdkit execution
|
||||
Debug: true,
|
||||
MountedVolumes: []MountedVolume{},
|
||||
}
|
||||
|
||||
@@ -248,6 +248,8 @@ func TestIntegration_EBSUnmountRequest(t *testing.T) {
|
||||
|
||||
// TestIntegration_EBSUnmountNonExistentVolume tests unmounting a volume that doesn't exist
|
||||
func TestIntegration_EBSUnmountNonExistentVolume(t *testing.T) {
|
||||
|
||||
// Skip
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user