CRITICAL CHECKPOINT - DO NOT PUSH TO GITHUB Recovery Status: - Successfully recovered brainy.ts from compiled JavaScript - All core v3.0 API methods functional (add, get, update, delete, relate, find, etc.) - Neural subsystem intact (562KB embedded patterns, NLP working) - Augmentation pipeline operational (20+ augmentations) - HNSW clustering system complete - Triple Intelligence compiled (needs constructor fix) - Test suite validates functionality Changes preserved: - 898 files with changes from last 3 days - 144,475 insertions - All augmentation improvements - All test coverage enhancements - Complete v3.0 feature set This is a LOCAL checkpoint only - contains recovered work after corruption incident. Created backup in .backups/brainy-full-20250910-151314.tar.gz Branch: recovery-checkpoint-20250910-151433 Date: Wed Sep 10 03:18:04 PM PDT 2025
720 lines
No EOL
18 KiB
Markdown
720 lines
No EOL
18 KiB
Markdown
# Cloud Storage & Distributed Features Testing Plan
|
|
|
|
## Executive Summary
|
|
|
|
This plan outlines industry-standard approaches for testing S3 storage and distributed features locally, without requiring real AWS credentials or complex infrastructure.
|
|
|
|
## Part 1: Cloud Storage Testing
|
|
|
|
### 🏆 Recommended Solution: MinIO
|
|
|
|
**MinIO** is the industry standard for local S3 testing:
|
|
- **100% S3 API compatible**
|
|
- **Used by**: Netflix, VMware, Alibaba
|
|
- **Weekly downloads**: 200k+ (Docker), 50k+ (npm)
|
|
- **Lightweight**: ~100MB Docker image
|
|
- **Production-ready**: Can be used as actual S3 replacement
|
|
|
|
### Implementation Plan
|
|
|
|
#### Option A: Docker-based MinIO (Recommended)
|
|
|
|
```yaml
|
|
# docker-compose.test.yml
|
|
version: '3.8'
|
|
services:
|
|
minio:
|
|
image: minio/minio:latest
|
|
ports:
|
|
- "9000:9000"
|
|
- "9001:9001"
|
|
environment:
|
|
MINIO_ROOT_USER: minioadmin
|
|
MINIO_ROOT_PASSWORD: minioadmin
|
|
command: server /data --console-address ":9001"
|
|
volumes:
|
|
- minio_data:/data
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
|
interval: 30s
|
|
timeout: 20s
|
|
retries: 3
|
|
|
|
volumes:
|
|
minio_data:
|
|
```
|
|
|
|
**Test Setup Script:**
|
|
```bash
|
|
#!/bin/bash
|
|
# scripts/test-s3.sh
|
|
|
|
# Start MinIO
|
|
docker-compose -f docker-compose.test.yml up -d minio
|
|
|
|
# Wait for MinIO to be ready
|
|
echo "Waiting for MinIO to start..."
|
|
until docker-compose -f docker-compose.test.yml exec -T minio curl -f http://localhost:9000/minio/health/live; do
|
|
sleep 1
|
|
done
|
|
|
|
# Create test bucket
|
|
docker-compose -f docker-compose.test.yml exec -T minio mc alias set local http://localhost:9000 minioadmin minioadmin
|
|
docker-compose -f docker-compose.test.yml exec -T minio mc mb local/test-bucket
|
|
|
|
# Run tests
|
|
npm test -- tests/integration/s3-storage.test.ts
|
|
|
|
# Cleanup
|
|
docker-compose -f docker-compose.test.yml down
|
|
```
|
|
|
|
#### Option B: Node.js MinIO Server (CI-Friendly)
|
|
|
|
```typescript
|
|
// tests/helpers/minio-server.ts
|
|
import * as Minio from 'minio'
|
|
import { spawn } from 'child_process'
|
|
import { mkdirSync } from 'fs'
|
|
import { tmpdir } from 'os'
|
|
import { join } from 'path'
|
|
|
|
export class MinioTestServer {
|
|
private process: any
|
|
private client: Minio.Client
|
|
private dataDir: string
|
|
|
|
async start(): Promise<void> {
|
|
// Create temp data directory
|
|
this.dataDir = join(tmpdir(), `minio-test-${Date.now()}`)
|
|
mkdirSync(this.dataDir, { recursive: true })
|
|
|
|
// Start MinIO server
|
|
this.process = spawn('minio', [
|
|
'server',
|
|
this.dataDir,
|
|
'--address', ':9000'
|
|
], {
|
|
env: {
|
|
...process.env,
|
|
MINIO_ROOT_USER: 'minioadmin',
|
|
MINIO_ROOT_PASSWORD: 'minioadmin'
|
|
}
|
|
})
|
|
|
|
// Wait for server to be ready
|
|
await this.waitForServer()
|
|
|
|
// Create client
|
|
this.client = new Minio.Client({
|
|
endPoint: 'localhost',
|
|
port: 9000,
|
|
useSSL: false,
|
|
accessKey: 'minioadmin',
|
|
secretKey: 'minioadmin'
|
|
})
|
|
|
|
// Create test bucket
|
|
await this.client.makeBucket('test-bucket', 'us-east-1')
|
|
}
|
|
|
|
async stop(): Promise<void> {
|
|
if (this.process) {
|
|
this.process.kill()
|
|
}
|
|
// Clean up data directory
|
|
await fs.rm(this.dataDir, { recursive: true, force: true })
|
|
}
|
|
|
|
private async waitForServer(maxRetries = 30): Promise<void> {
|
|
for (let i = 0; i < maxRetries; i++) {
|
|
try {
|
|
await fetch('http://localhost:9000/minio/health/live')
|
|
return
|
|
} catch {
|
|
await new Promise(r => setTimeout(r, 1000))
|
|
}
|
|
}
|
|
throw new Error('MinIO server failed to start')
|
|
}
|
|
|
|
getConfig(): any {
|
|
return {
|
|
type: 's3',
|
|
options: {
|
|
endpoint: 'http://localhost:9000',
|
|
bucket: 'test-bucket',
|
|
region: 'us-east-1',
|
|
credentials: {
|
|
accessKeyId: 'minioadmin',
|
|
secretAccessKey: 'minioadmin'
|
|
},
|
|
forcePathStyle: true // Important for MinIO
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### S3 Storage Test Suite
|
|
|
|
```typescript
|
|
// tests/integration/s3-storage.test.ts
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
|
import { Brainy } from '../../src/brainy.js'
|
|
import { MinioTestServer } from '../helpers/minio-server.js'
|
|
|
|
describe('S3 Storage Adapter', () => {
|
|
let minio: MinioTestServer
|
|
let brain: Brainy
|
|
|
|
beforeAll(async () => {
|
|
// Start MinIO server
|
|
minio = new MinioTestServer()
|
|
await minio.start()
|
|
|
|
// Create Brainy with S3 storage
|
|
brain = new Brainy({
|
|
storage: minio.getConfig()
|
|
})
|
|
await brain.init()
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await minio.stop()
|
|
})
|
|
|
|
it('should store and retrieve data', async () => {
|
|
const id = await brain.add({
|
|
data: 'S3 test data',
|
|
type: 'document'
|
|
})
|
|
|
|
const retrieved = await brain.get(id)
|
|
expect(retrieved).toBeDefined()
|
|
expect(retrieved.id).toBe(id)
|
|
})
|
|
|
|
it('should persist data across restarts', async () => {
|
|
const id = await brain.add({
|
|
data: 'Persistent S3 data',
|
|
metadata: { important: true }
|
|
})
|
|
|
|
// Create new instance with same config
|
|
const brain2 = new Brainy({
|
|
storage: minio.getConfig()
|
|
})
|
|
await brain2.init()
|
|
|
|
const retrieved = await brain2.get(id)
|
|
expect(retrieved).toBeDefined()
|
|
expect(retrieved.metadata.important).toBe(true)
|
|
})
|
|
|
|
it('should handle large files efficiently', async () => {
|
|
const largeData = 'x'.repeat(10 * 1024 * 1024) // 10MB
|
|
|
|
const start = Date.now()
|
|
const id = await brain.add({
|
|
data: largeData,
|
|
type: 'document'
|
|
})
|
|
const uploadTime = Date.now() - start
|
|
|
|
expect(uploadTime).toBeLessThan(5000) // Should upload in < 5s
|
|
|
|
const retrieved = await brain.get(id)
|
|
expect(retrieved).toBeDefined()
|
|
})
|
|
|
|
it('should handle concurrent operations', async () => {
|
|
const operations = Array(50).fill(0).map((_, i) =>
|
|
brain.add({ data: `Concurrent ${i}` })
|
|
)
|
|
|
|
const ids = await Promise.all(operations)
|
|
expect(ids).toHaveLength(50)
|
|
expect(new Set(ids).size).toBe(50) // All unique
|
|
})
|
|
|
|
it('should handle network errors gracefully', async () => {
|
|
// Stop MinIO to simulate network failure
|
|
await minio.stop()
|
|
|
|
// Operations should fail gracefully
|
|
await expect(brain.add({ data: 'Will fail' }))
|
|
.rejects.toThrow()
|
|
|
|
// Restart MinIO
|
|
await minio.start()
|
|
|
|
// Should recover
|
|
const id = await brain.add({ data: 'Recovered' })
|
|
expect(id).toBeDefined()
|
|
})
|
|
})
|
|
```
|
|
|
|
## Part 2: Distributed Features Testing
|
|
|
|
### 🏆 Recommended Solution: TestContainers + Redis
|
|
|
|
**TestContainers** is the industry standard for integration testing:
|
|
- Used by Spring, Quarkus, major enterprises
|
|
- Automatic container lifecycle management
|
|
- Built-in wait strategies
|
|
- Network isolation
|
|
|
|
### Implementation Plan
|
|
|
|
#### 1. Install Dependencies
|
|
|
|
```json
|
|
{
|
|
"devDependencies": {
|
|
"testcontainers": "^10.2.1",
|
|
"redis": "^4.6.10",
|
|
"@testcontainers/redis": "^10.2.1",
|
|
"minio": "^8.0.5"
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 2. Distributed Test Helper
|
|
|
|
```typescript
|
|
// tests/helpers/distributed-cluster.ts
|
|
import { GenericContainer, Network, StartedTestContainer } from 'testcontainers'
|
|
import { RedisContainer } from '@testcontainers/redis'
|
|
|
|
export class DistributedTestCluster {
|
|
private network: Network
|
|
private redis: StartedTestContainer
|
|
private nodes: Brainy[] = []
|
|
|
|
async start(nodeCount: number = 3): Promise<void> {
|
|
// Create isolated network
|
|
this.network = await new Network().start()
|
|
|
|
// Start Redis for coordination
|
|
this.redis = await new RedisContainer()
|
|
.withNetwork(this.network)
|
|
.withNetworkAliases('redis')
|
|
.start()
|
|
|
|
// Start multiple Brainy nodes
|
|
for (let i = 0; i < nodeCount; i++) {
|
|
const node = new Brainy({
|
|
distributed: {
|
|
enabled: true,
|
|
nodeId: `node-${i}`,
|
|
redis: {
|
|
host: this.redis.getHost(),
|
|
port: this.redis.getMappedPort(6379)
|
|
},
|
|
role: i === 0 ? 'primary' : 'replica'
|
|
},
|
|
storage: { type: 'memory' }
|
|
})
|
|
|
|
await node.init()
|
|
this.nodes.push(node)
|
|
}
|
|
}
|
|
|
|
async stop(): Promise<void> {
|
|
for (const node of this.nodes) {
|
|
await node.shutdown?.()
|
|
}
|
|
await this.redis.stop()
|
|
await this.network.stop()
|
|
}
|
|
|
|
getPrimary(): Brainy {
|
|
return this.nodes[0]
|
|
}
|
|
|
|
getReplicas(): Brainy[] {
|
|
return this.nodes.slice(1)
|
|
}
|
|
|
|
getAllNodes(): Brainy[] {
|
|
return this.nodes
|
|
}
|
|
|
|
async simulateNodeFailure(index: number): Promise<void> {
|
|
if (this.nodes[index]) {
|
|
await this.nodes[index].shutdown?.()
|
|
this.nodes.splice(index, 1)
|
|
}
|
|
}
|
|
|
|
async addNode(): Promise<Brainy> {
|
|
const node = new Brainy({
|
|
distributed: {
|
|
enabled: true,
|
|
nodeId: `node-${this.nodes.length}`,
|
|
redis: {
|
|
host: this.redis.getHost(),
|
|
port: this.redis.getMappedPort(6379)
|
|
},
|
|
role: 'replica'
|
|
},
|
|
storage: { type: 'memory' }
|
|
})
|
|
|
|
await node.init()
|
|
this.nodes.push(node)
|
|
return node
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 3. Distributed Features Test Suite
|
|
|
|
```typescript
|
|
// tests/integration/distributed.test.ts
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
|
import { DistributedTestCluster } from '../helpers/distributed-cluster.js'
|
|
|
|
describe('Distributed Features', () => {
|
|
let cluster: DistributedTestCluster
|
|
|
|
beforeAll(async () => {
|
|
cluster = new DistributedTestCluster()
|
|
await cluster.start(3) // Start with 3 nodes
|
|
}, 30000) // Extended timeout for container startup
|
|
|
|
afterAll(async () => {
|
|
await cluster.stop()
|
|
})
|
|
|
|
describe('Data Replication', () => {
|
|
it('should replicate data across nodes', async () => {
|
|
const primary = cluster.getPrimary()
|
|
const replicas = cluster.getReplicas()
|
|
|
|
// Add data to primary
|
|
const id = await primary.add({
|
|
data: 'Replicated data',
|
|
metadata: { timestamp: Date.now() }
|
|
})
|
|
|
|
// Wait for replication
|
|
await new Promise(r => setTimeout(r, 1000))
|
|
|
|
// Verify data on replicas
|
|
for (const replica of replicas) {
|
|
const data = await replica.get(id)
|
|
expect(data).toBeDefined()
|
|
expect(data.id).toBe(id)
|
|
}
|
|
})
|
|
|
|
it('should handle read distribution', async () => {
|
|
const nodes = cluster.getAllNodes()
|
|
|
|
// Add test data
|
|
const primary = cluster.getPrimary()
|
|
const ids = await Promise.all(
|
|
Array(100).fill(0).map((_, i) =>
|
|
primary.add({ data: `Item ${i}` })
|
|
)
|
|
)
|
|
|
|
// Perform reads from all nodes
|
|
const readPromises = nodes.flatMap(node =>
|
|
ids.slice(0, 10).map(id => node.get(id))
|
|
)
|
|
|
|
const results = await Promise.all(readPromises)
|
|
expect(results.every(r => r !== null)).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('Failover', () => {
|
|
it('should handle primary node failure', async () => {
|
|
const primary = cluster.getPrimary()
|
|
|
|
// Add data before failure
|
|
const id = await primary.add({
|
|
data: 'Pre-failure data'
|
|
})
|
|
|
|
// Simulate primary failure
|
|
await cluster.simulateNodeFailure(0)
|
|
|
|
// Remaining nodes should still function
|
|
const newPrimary = cluster.getAllNodes()[0]
|
|
const retrieved = await newPrimary.get(id)
|
|
expect(retrieved).toBeDefined()
|
|
|
|
// Should be able to write to new primary
|
|
const newId = await newPrimary.add({
|
|
data: 'Post-failure data'
|
|
})
|
|
expect(newId).toBeDefined()
|
|
})
|
|
|
|
it('should handle node recovery', async () => {
|
|
// Add a new node
|
|
const newNode = await cluster.addNode()
|
|
|
|
// Should sync existing data
|
|
await new Promise(r => setTimeout(r, 2000))
|
|
|
|
// Verify node has data
|
|
const primary = cluster.getPrimary()
|
|
const id = await primary.add({ data: 'Test sync' })
|
|
|
|
await new Promise(r => setTimeout(r, 1000))
|
|
|
|
const data = await newNode.get(id)
|
|
expect(data).toBeDefined()
|
|
})
|
|
})
|
|
|
|
describe('Distributed Search', () => {
|
|
it('should coordinate search across nodes', async () => {
|
|
const primary = cluster.getPrimary()
|
|
|
|
// Add diverse data
|
|
for (let i = 0; i < 30; i++) {
|
|
await primary.add({
|
|
data: `Document ${i} about ${['tech', 'science', 'business'][i % 3]}`,
|
|
metadata: { category: ['tech', 'science', 'business'][i % 3] }
|
|
})
|
|
}
|
|
|
|
// Search from different nodes
|
|
const nodes = cluster.getAllNodes()
|
|
const searchPromises = nodes.map(node =>
|
|
node.find({ query: 'tech', limit: 5 })
|
|
)
|
|
|
|
const results = await Promise.all(searchPromises)
|
|
|
|
// All nodes should return consistent results
|
|
const firstResult = results[0]
|
|
for (const result of results) {
|
|
expect(result.length).toBe(firstResult.length)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('Cache Synchronization', () => {
|
|
it('should synchronize cache invalidation', async () => {
|
|
const nodes = cluster.getAllNodes()
|
|
|
|
// Warm up caches
|
|
const id = await nodes[0].add({ data: 'Cached item' })
|
|
|
|
// All nodes query to populate cache
|
|
await Promise.all(nodes.map(n => n.find({ query: 'Cached' })))
|
|
|
|
// Update data on one node
|
|
await nodes[0].update({ id, data: 'Updated cached item' })
|
|
|
|
// Wait for cache invalidation to propagate
|
|
await new Promise(r => setTimeout(r, 500))
|
|
|
|
// All nodes should see updated data
|
|
const searches = await Promise.all(
|
|
nodes.map(n => n.find({ query: 'Updated cached' }))
|
|
)
|
|
|
|
for (const search of searches) {
|
|
expect(search.length).toBeGreaterThan(0)
|
|
}
|
|
})
|
|
})
|
|
})
|
|
```
|
|
|
|
## Part 3: CLI Testing & Fixes
|
|
|
|
### CLI Test Setup
|
|
|
|
```typescript
|
|
// tests/cli/cli.test.ts
|
|
import { describe, it, expect } from 'vitest'
|
|
import { spawn } from 'child_process'
|
|
import { promisify } from 'util'
|
|
|
|
const exec = promisify(require('child_process').exec)
|
|
|
|
describe('CLI Commands', () => {
|
|
const CLI_PATH = './bin/brainy.js'
|
|
|
|
async function runCLI(args: string): Promise<{ stdout: string, stderr: string }> {
|
|
return exec(`node ${CLI_PATH} ${args}`)
|
|
}
|
|
|
|
it('should show help', async () => {
|
|
const { stdout } = await runCLI('--help')
|
|
expect(stdout).toContain('brainy')
|
|
expect(stdout).toContain('Commands:')
|
|
})
|
|
|
|
it('should add items', async () => {
|
|
const { stdout } = await runCLI('add "Test item"')
|
|
expect(stdout).toContain('Added')
|
|
expect(stdout).toMatch(/[a-f0-9-]{36}/) // UUID
|
|
})
|
|
|
|
it('should search items', async () => {
|
|
await runCLI('add "JavaScript tutorial"')
|
|
const { stdout } = await runCLI('search "JavaScript"')
|
|
expect(stdout).toContain('JavaScript')
|
|
})
|
|
|
|
it('should list augmentations', async () => {
|
|
const { stdout } = await runCLI('augment list')
|
|
expect(stdout).toContain('cache')
|
|
expect(stdout).toContain('index')
|
|
})
|
|
|
|
it('should show catalog', async () => {
|
|
const { stdout } = await runCLI('catalog')
|
|
expect(stdout).toContain('Augmentation Catalog')
|
|
})
|
|
})
|
|
```
|
|
|
|
## Part 4: GitHub Actions CI Integration
|
|
|
|
```yaml
|
|
# .github/workflows/test.yml
|
|
name: Tests
|
|
|
|
on: [push, pull_request]
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
|
|
services:
|
|
minio:
|
|
image: minio/minio:latest
|
|
env:
|
|
MINIO_ROOT_USER: minioadmin
|
|
MINIO_ROOT_PASSWORD: minioadmin
|
|
ports:
|
|
- 9000:9000
|
|
options: --health-cmd "curl -f http://localhost:9000/minio/health/live"
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
ports:
|
|
- 6379:6379
|
|
options: --health-cmd "redis-cli ping"
|
|
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
|
|
- uses: actions/setup-node@v3
|
|
with:
|
|
node-version: '18'
|
|
cache: 'npm'
|
|
|
|
- run: npm ci
|
|
|
|
- name: Setup MinIO
|
|
run: |
|
|
curl -O https://dl.min.io/client/mc/release/linux-amd64/mc
|
|
chmod +x mc
|
|
./mc alias set minio http://localhost:9000 minioadmin minioadmin
|
|
./mc mb minio/test-bucket
|
|
|
|
- name: Run Tests
|
|
env:
|
|
MINIO_ENDPOINT: http://localhost:9000
|
|
REDIS_HOST: localhost
|
|
run: |
|
|
npm run test:unit
|
|
npm run test:integration
|
|
npm run test:cli
|
|
```
|
|
|
|
## Implementation Timeline
|
|
|
|
### Phase 1: Setup (Day 1)
|
|
- [ ] Install MinIO and TestContainers
|
|
- [ ] Create test helpers
|
|
- [ ] Setup Docker Compose files
|
|
|
|
### Phase 2: Cloud Storage Tests (Day 2)
|
|
- [ ] Implement MinIO test server
|
|
- [ ] Write S3 adapter tests
|
|
- [ ] Test R2 compatibility (uses S3 API)
|
|
- [ ] Add error handling tests
|
|
|
|
### Phase 3: Distributed Tests (Day 3)
|
|
- [ ] Setup distributed cluster helper
|
|
- [ ] Test data replication
|
|
- [ ] Test failover scenarios
|
|
- [ ] Test cache synchronization
|
|
|
|
### Phase 4: CLI Fixes & Tests (Day 4)
|
|
- [ ] Fix TypeScript compilation issues
|
|
- [ ] Add missing commands
|
|
- [ ] Write CLI test suite
|
|
- [ ] Test all commands
|
|
|
|
### Phase 5: CI Integration (Day 5)
|
|
- [ ] Setup GitHub Actions
|
|
- [ ] Configure test matrix
|
|
- [ ] Add coverage reporting
|
|
- [ ] Documentation
|
|
|
|
## Cost-Benefit Analysis
|
|
|
|
### Benefits
|
|
- **100% local testing** - No AWS costs
|
|
- **CI/CD ready** - Automated testing
|
|
- **Production confidence** - Real S3 API testing
|
|
- **Fast feedback** - Tests run in seconds
|
|
- **Reproducible** - Same tests everywhere
|
|
|
|
### Tools Comparison
|
|
|
|
| Tool | Pros | Cons | Recommendation |
|
|
|------|------|------|----------------|
|
|
| **MinIO** | Full S3 API, Production-ready, Fast | Requires Docker/Binary | ✅ **USE THIS** |
|
|
| LocalStack | Multiple AWS services | Heavy, Complex, Paid features | ❌ Overkill |
|
|
| S3Mock | Pure Java, Simple | Limited API coverage | ❌ Incomplete |
|
|
| AWS S3 (real) | 100% compatible | Costs money, Slow, Network dependent | ❌ Not for tests |
|
|
|
|
## Quick Start Commands
|
|
|
|
```bash
|
|
# Install dependencies
|
|
npm install --save-dev minio testcontainers @testcontainers/redis
|
|
|
|
# Run S3 tests with MinIO
|
|
docker run -p 9000:9000 -p 9001:9001 minio/minio server /data --console-address ":9001"
|
|
npm test -- tests/integration/s3-storage.test.ts
|
|
|
|
# Run distributed tests
|
|
npm test -- tests/integration/distributed.test.ts
|
|
|
|
# Run all integration tests
|
|
npm run test:integration
|
|
|
|
# Run with coverage
|
|
npm run test:integration -- --coverage
|
|
```
|
|
|
|
## Conclusion
|
|
|
|
This plan provides **industry-standard testing** for both cloud storage and distributed features using:
|
|
- **MinIO** for S3 testing (used by Netflix, VMware)
|
|
- **TestContainers** for distributed testing (used by Spring Boot)
|
|
- **Real APIs** not mocks
|
|
- **CI/CD ready** with GitHub Actions
|
|
- **Zero cloud costs** for testing
|
|
|
|
Total implementation time: **5 days**
|
|
Total additional dependencies: **3** (minio, testcontainers, redis)
|
|
Confidence level: **95%** coverage of production scenarios |