package main import ( "encoding/json" "os" "path/filepath" "testing" "github.com/DATA-DOG/go-sqlmock" ) func expectSnapshot(mock sqlmock.Sqlmock, notified bool) { mock.ExpectBegin() mock.ExpectQuery(`SELECT \* FROM vehicle_alert_event`).WillReturnRows(sqlmock.NewRows([]string{"id", "rule_name", "status"}).AddRow("false-event", "车辆原生告警", "unprocessed").AddRow("real-event", "车辆原生告警", "processing")) payload := func(flag string) string { data := map[string]any{"gb32960.alarm.max_alarm_level": "0", "gb32960.alarm.general_alarm_flag": flag} for _, key := range []string{"battery_faults", "motor_faults", "engine_faults", "other_faults"} { data["gb32960.alarm."+key] = []string{} } b, _ := json.Marshal(data) return string(b) } mock.ExpectQuery(`SELECT n.\* FROM vehicle_native_alarm_evidence`).WillReturnRows(sqlmock.NewRows([]string{"event_id", "fields_json"}).AddRow("false-event", payload("0x00300000")).AddRow("real-event", payload("0x00380800"))) mock.ExpectQuery(`SELECT a.\* FROM vehicle_alert_event_action`).WillReturnRows(sqlmock.NewRows([]string{"event_id", "action"}).AddRow("false-event", "trigger")) mock.ExpectQuery(`SELECT s.\* FROM vehicle_native_alarm_state`).WillReturnRows(sqlmock.NewRows([]string{"vin", "active_event_id"}).AddRow("VIN1", "false-event")) notifications := sqlmock.NewRows([]string{"id", "event_id"}) if notified { notifications.AddRow(1, "false-event") } mock.ExpectQuery(`SELECT n.id,n.event_id`).WillReturnRows(notifications) } func TestRepairDryRunNeverWrites(t *testing.T) { db, mock, _ := sqlmock.New() defer db.Close() expectSnapshot(mock, false) mock.ExpectRollback() if err := run(t.Context(), db, false, ""); err != nil { t.Fatal(err) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestRepairBacksUpAndCorrectsOnlyNativeEvents(t *testing.T) { db, mock, _ := sqlmock.New() defer db.Close() expectSnapshot(mock, false) mock.ExpectExec(`UPDATE vehicle_native_alarm_state`).WithArgs("false-event").WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectExec(`DELETE FROM vehicle_alert_event_action`).WithArgs("false-event").WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectExec(`DELETE FROM vehicle_native_alarm_evidence`).WithArgs("false-event").WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectExec(`DELETE FROM vehicle_alert_event WHERE id=\? AND rule_id='native-gb32960-alarm'`).WithArgs("false-event").WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectExec(`UPDATE vehicle_alert_event SET rule_name=\?,version=version\+1 WHERE id=\? AND rule_id='native-gb32960-alarm'`).WithArgs("绝缘报警", "real-event").WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectExec(`INSERT INTO vehicle_alert_event_action`).WithArgs("real-event", "processing", "processing").WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() path := filepath.Join(t.TempDir(), "backup.json") if err := run(t.Context(), db, true, path); err != nil { t.Fatal(err) } data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } var archive map[string]json.RawMessage if err = json.Unmarshal(data, &archive); err != nil { t.Fatal(err) } for _, key := range []string{"events", "evidence", "actions", "state"} { if len(archive[key]) == 0 { t.Fatalf("missing backup %s", key) } } info, _ := os.Stat(path) if info.Mode().Perm() != 0600 { t.Fatal("backup permissions") } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestRepairRefusesToOverwriteBackup(t *testing.T) { db, mock, _ := sqlmock.New() defer db.Close() expectSnapshot(mock, false) mock.ExpectRollback() path := filepath.Join(t.TempDir(), "backup.json") if err := os.WriteFile(path, []byte("previous backup"), 0600); err != nil { t.Fatal(err) } if err := run(t.Context(), db, true, path); err == nil { t.Fatal("overwrote backup") } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestRepairRefusesToOrphanNotifications(t *testing.T) { db, mock, _ := sqlmock.New() defer db.Close() expectSnapshot(mock, true) mock.ExpectRollback() if err := run(t.Context(), db, true, filepath.Join(t.TempDir(), "backup.json")); err == nil { t.Fatal("deleted notified event") } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } }