package gzev import ( "context" "encoding/json" "errors" "github.com/DATA-DOG/go-sqlmock" "testing" "time" ) func TestSaveDayRollsBackIfAnyRowFails(t *testing.T) { db, mock, _ := sqlmock.New() defer db.Close() conn, _ := db.Conn(context.Background()) defer conn.Close() store := Store{conn, "account"} mock.ExpectBegin() mock.ExpectQuery("SELECT vin").WithArgs("account", "2024-01-01").WillReturnRows(sqlmock.NewRows([]string{"vin", "total", "hydrogen", "charge"})) mock.ExpectExec("INSERT IGNORE INTO gzev_mileage_snapshot").WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectExec("DELETE FROM gzev_daily_mileage").WillReturnResult(sqlmock.NewResult(0, 0)) mock.ExpectExec("INSERT INTO gzev_daily_mileage").WillReturnError(errors.New("disk full")) mock.ExpectRollback() d := Day{Date: "2024-01-01", FetchedAt: time.Now(), Pages: []json.RawMessage{json.RawMessage(`{}`)}, Rows: []Row{{VIN: "LNXNEGRR7SR319456", Raw: json.RawMessage(`{}`)}}} if store.SaveDay(context.Background(), d) == nil { t.Fatal("expected transaction failure") } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestSaveRejectsNullRegression(t *testing.T) { db, mock, _ := sqlmock.New() defer db.Close() conn, _ := db.Conn(context.Background()) defer conn.Close() store := Store{conn, "account"} mock.ExpectBegin() mock.ExpectQuery("SELECT vin").WillReturnRows(sqlmock.NewRows([]string{"vin", "total", "hydrogen", "charge"}).AddRow("LNXNEGRR7SR319456", true, false, false)) mock.ExpectRollback() if store.SaveDay(context.Background(), Day{Date: "2024-01-01", Rows: []Row{{VIN: "LNXNEGRR7SR319456"}}}) == nil { t.Fatal("overwrote measured value with null") } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestAuthNotificationCooldown(t *testing.T) { db, mock, _ := sqlmock.New() defer db.Close() conn, _ := db.Conn(context.Background()) defer conn.Close() store := Store{conn, "account"} mock.ExpectExec("INSERT INTO gzev_auth_alert").WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectQuery("SELECT .*last_attempt_at").WillReturnRows(sqlmock.NewRows([]string{"due"}).AddRow(false)) if err := store.AuthFailure(context.Background(), func(context.Context) error { t.Fatal("duplicate SMS sent"); return nil }); err != nil { t.Fatal(err) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestAuthNotificationFailureIsPersisted(t *testing.T) { db, mock, _ := sqlmock.New() defer db.Close() conn, _ := db.Conn(context.Background()) defer conn.Close() store := Store{conn, "account"} mock.ExpectExec("INSERT INTO gzev_auth_alert").WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectQuery("SELECT .*last_attempt_at").WillReturnRows(sqlmock.NewRows([]string{"due"}).AddRow(true)) mock.ExpectExec("UPDATE gzev_auth_alert SET last_attempt_at").WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectExec("UPDATE gzev_auth_alert SET last_error").WillReturnResult(sqlmock.NewResult(0, 1)) if err := store.AuthFailure(context.Background(), func(context.Context) error { return errors.New("not configured") }); err == nil { t.Fatal("lost delivery failure") } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } }