better end points and better tests
This commit is contained in:
@@ -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()})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user