feat: containerize
This commit is contained in:
parent
83bb72faac
commit
82a78da81b
10 changed files with 319 additions and 55 deletions
140
drone-black-box/main_test.go
Normal file
140
drone-black-box/main_test.go
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
// create an in-memory DB with schema/index identical to main
|
||||
func newTestDB(t *testing.T) *sql.DB {
|
||||
t.Helper()
|
||||
db, err := sql.Open("sqlite", "file:testdb?mode=memory&cache=shared")
|
||||
if err != nil {
|
||||
t.Fatalf("open db: %v", err)
|
||||
}
|
||||
schema := `CREATE TABLE IF NOT EXISTS drone_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
drone_id TEXT NOT NULL,
|
||||
latitude REAL NOT NULL,
|
||||
longitude REAL NOT NULL,
|
||||
timestamp TEXT NOT NULL
|
||||
);`
|
||||
if _, err := db.Exec(schema); err != nil {
|
||||
t.Fatalf("schema: %v", err)
|
||||
}
|
||||
if _, err := db.Exec(`CREATE INDEX IF NOT EXISTS idx_drone_timestamp ON drone_events(drone_id, timestamp);`); err != nil {
|
||||
t.Fatalf("index: %v", err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func TestIngestAndSnapshot(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
srv := &Server{db: db}
|
||||
|
||||
// ingest two drones with multiple timestamps
|
||||
events := []DroneEvent{
|
||||
{"d1", 1.0, 2.0, "2025-12-06T00:00:00Z"},
|
||||
{"d1", 1.1, 2.1, "2025-12-06T00:00:10Z"},
|
||||
{"d2", 3.0, 4.0, "2025-12-06T00:00:05Z"},
|
||||
}
|
||||
for _, ev := range events {
|
||||
body, _ := json.Marshal(ev)
|
||||
req := httptest.NewRequest(http.MethodPost, "/ingest", bytes.NewReader(body))
|
||||
rec := httptest.NewRecorder()
|
||||
srv.ingestHandler(rec, req)
|
||||
if rec.Code != http.StatusCreated {
|
||||
t.Fatalf("ingest %v code=%d body=%s", ev, rec.Code, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// snapshot at 00:00:07 should pick d1@00:00:00? no 00:00:10 too new -> d1@0, d2@5
|
||||
q := url.Values{}
|
||||
q.Set("time", "2025-12-06T00:00:07Z")
|
||||
req := httptest.NewRequest(http.MethodGet, "/snapshot?"+q.Encode(), nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.snapshotHandler(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("snapshot code=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
var resp []DroneEvent
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("decode snapshot: %v", err)
|
||||
}
|
||||
if len(resp) != 2 {
|
||||
t.Fatalf("expected 2 events, got %d", len(resp))
|
||||
}
|
||||
// map by drone
|
||||
got := map[string]DroneEvent{}
|
||||
for _, e := range resp {
|
||||
got[e.DroneID] = e
|
||||
}
|
||||
if got["d1"].Timestamp != "2025-12-06T00:00:00Z" {
|
||||
t.Fatalf("d1 timestamp mismatch: %v", got["d1"])
|
||||
}
|
||||
if got["d2"].Timestamp != "2025-12-06T00:00:05Z" {
|
||||
t.Fatalf("d2 timestamp mismatch: %v", got["d2"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapshotMissingTime(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
srv := &Server{db: db}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/snapshot", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.snapshotHandler(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealth(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
srv := &Server{db: db}
|
||||
req := httptest.NewRequest(http.MethodGet, "/health", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.healthHandler(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("health code=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngestBadJSON(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
srv := &Server{db: db}
|
||||
req := httptest.NewRequest(http.MethodPost, "/ingest", strings.NewReader("{bad"))
|
||||
rec := httptest.NewRecorder()
|
||||
srv.ingestHandler(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure graceful shutdown path does not hang: start a server and shut it down quickly.
|
||||
func TestGracefulShutdown(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
srv := &Server{db: db}
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /health", srv.healthHandler)
|
||||
testSrv := &http.Server{Addr: "127.0.0.1:0", Handler: mux}
|
||||
|
||||
go func() { _ = testSrv.ListenAndServe() }()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
if err := testSrv.Shutdown(ctx); err != nil {
|
||||
t.Fatalf("shutdown: %v", err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue