better end points and better tests

This commit is contained in:
samantha42
2026-03-21 15:47:40 +01:00
parent 3f203178b2
commit 3490dd13d4
10 changed files with 482 additions and 34 deletions

View File

@@ -8,6 +8,7 @@ import (
"Engine/internal/database"
"Engine/internal/handler"
"Engine/internal/model"
"Engine/internal/service"
"Engine/tests/internal/testutil"
)
@@ -39,18 +40,15 @@ func newFullServer(t *testing.T) *httptest.Server {
return srv
}
func newActualsHandler(t *testing.T) *handler.ActualsHandler {
t.Helper()
return handler.NewActualsHandler(database.NewActualsRepo(testutil.NewTestDB(t)))
}
// validActual returns one well-formed actual record.
func validActual() map[string]any {
return map[string]any{
"department_id": 1,
"gl_account_id": 1,
"period": "2024-01",
"dept_code": "TEST",
"gl_code": "TEST",
"fiscal_year": 2024,
"fiscal_period": 1,
"amount": 1234.56,
"currency": "USD",
"source": "csv_import",
}
}
@@ -58,43 +56,72 @@ func validActual() map[string]any {
// ── Actuals: Ingest ───────────────────────────────────────────────────────────
func TestIngestActuals_SingleRecord(t *testing.T) {
h := newActualsHandler(t)
t.Helper()
db := testutil.NewTestDB(t)
repo := database.NewActualsRepo(db)
h := handler.NewActualsHandler(repo)
w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/",
[]any{validActual()})
// makes sire the dept and gl entries exists for testing
testutil.SeedFixtures(t, db) // seld error handling/return
w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", validActual())
testutil.AssertStatus(t, w, http.StatusCreated)
var got model.Actual
testutil.DecodeJSON(t, w, &got)
if got.ID == 0 {
t.Error("expected non-zero ID")
}
if got.Amount != 1234.56 {
t.Errorf("amount: got %v, want 1234.56", got.Amount)
}
}
func TestIngestActuals_MultipleRecords(t *testing.T) {
h := newActualsHandler(t)
db := testutil.NewTestDB(t)
testutil.SeedFixtures(t, db)
h := handler.NewActualsHandler(database.NewActualsRepo(db))
records := []any{
map[string]any{"department_id": 1, "gl_account_id": 1, "period": "2024-01", "amount": 100.00, "source": "csv"},
map[string]any{"department_id": 1, "gl_account_id": 2, "period": "2024-01", "amount": 200.00, "source": "csv"},
map[string]any{"department_id": 2, "gl_account_id": 1, "period": "2024-01", "amount": 300.00, "source": "csv"},
map[string]any{"dept_code": "TEST", "gl_code": "TEST", "fiscal_year": 2024, "fiscal_period": 1, "amount": 100.00, "currency": "DKK", "source": "csv"},
map[string]any{"dept_code": "TEST", "gl_code": "TEST", "fiscal_year": 2024, "fiscal_period": 2, "amount": 200.00, "currency": "DKK", "source": "csv"},
map[string]any{"dept_code": "TEST", "gl_code": "TEST", "fiscal_year": 2024, "fiscal_period": 3, "amount": 300.00, "currency": "DKK", "source": "csv"},
}
w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", records)
w := testutil.Do(t, http.HandlerFunc(h.IngestBatch), http.MethodPost, "/", records)
testutil.AssertStatus(t, w, http.StatusCreated)
var got []model.Actual
testutil.DecodeJSON(t, w, &got)
if len(got) != 3 {
t.Errorf("expected 3 results, got %d", len(got))
}
}
func TestIngestActuals_EmptyList(t *testing.T) {
h := newActualsHandler(t)
t.Helper()
db := testutil.NewTestDB(t)
repo := database.NewActualsRepo(db)
h := handler.NewActualsHandler(repo)
w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", []any{})
// Depending on your handler: 201 with 0 rows ingested, or 400
t.Logf("empty ingest: %d — verify against your handler", w.Code)
}
func TestIngestActuals_InvalidJSON(t *testing.T) {
h := newActualsHandler(t)
t.Helper()
db := testutil.NewTestDB(t)
repo := database.NewActualsRepo(db)
h := handler.NewActualsHandler(repo)
w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", nil)
testutil.AssertStatus(t, w, http.StatusBadRequest)
}
func TestIngestActuals_MissingPeriod(t *testing.T) {
h := newActualsHandler(t)
t.Helper()
db := testutil.NewTestDB(t)
repo := database.NewActualsRepo(db)
h := handler.NewActualsHandler(repo)
record := validActual()
delete(record, "period")
w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", []any{record})
@@ -102,7 +129,10 @@ func TestIngestActuals_MissingPeriod(t *testing.T) {
}
func TestIngestActuals_NegativeAmount(t *testing.T) {
h := newActualsHandler(t)
t.Helper()
db := testutil.NewTestDB(t)
repo := database.NewActualsRepo(db)
h := handler.NewActualsHandler(repo)
record := validActual()
record["amount"] = -500.00
w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", []any{record})
@@ -111,7 +141,10 @@ func TestIngestActuals_NegativeAmount(t *testing.T) {
}
func TestIngestActuals_Idempotent(t *testing.T) {
h := newActualsHandler(t)
t.Helper()
db := testutil.NewTestDB(t)
repo := database.NewActualsRepo(db)
h := handler.NewActualsHandler(repo)
fn := http.HandlerFunc(h.Ingest)
testutil.Do(t, fn, http.MethodPost, "/", []any{validActual()})

View File

@@ -26,8 +26,8 @@ func newBudgetServer(t *testing.T) *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc("POST /api/v1/budget/create", h.Create)
mux.HandleFunc("PUT /api/v1/budgets/update", h.Update)
mux.HandleFunc("DELETE /api/v1/budgets/delete", h.Delete)
mux.HandleFunc("PUT /api/v1/budget/update", h.Update)
mux.HandleFunc("DELETE /api/v1/budget/delete", h.Delete)
srv := httptest.NewServer(mux)
t.Cleanup(srv.Close)

View File

@@ -131,3 +131,38 @@ func DecodeJSON(t *testing.T, w *httptest.ResponseRecorder, dst any) {
t.Fatalf("DecodeJSON: %v\n\tbody: %s", err, w.Body.String())
}
}
// SeedFixtures inserts one department and one GL account into db and returns
// their IDs. Call this at the top of any test that needs FK-valid actuals or
// budget rows.
//
// Usage:
//
// deptID, glID := testutil.SeedFixtures(t, db)
func SeedFixtures(t *testing.T, db *sql.DB) (deptID int, glAccountID int) {
t.Helper()
res, err := db.Exec(`
INSERT INTO departments (code, name, cost_center, active)
VALUES ('TEST', 'Test Department', 'CC-TEST', 1)
ON CONFLICT(code) DO UPDATE SET name = excluded.name
`)
if err != nil {
t.Fatalf("SeedFixtures: insert department: %v", err)
}
id, _ := res.LastInsertId()
deptID = int(id)
res, err = db.Exec(`
INSERT INTO gl_accounts (code, description, type, favour_high, active)
VALUES ('TEST', 'Test Revenue', 'revenue', 1, 1)
ON CONFLICT(code) DO UPDATE SET description = excluded.description
`)
if err != nil {
t.Fatalf("SeedFixtures: insert gl_account: %v", err)
}
id, _ = res.LastInsertId()
glAccountID = int(id)
return deptID, glAccountID
}