| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632 |
- package store
- import (
- "bytes"
- "context"
- "database/sql"
- "encoding/json"
- "errors"
- "fmt"
- "path/filepath"
- "strings"
- "testing"
- "time"
- )
- func TestMigrationFromAuthenticationSchema(t *testing.T) {
- ctx := context.Background()
- path := filepath.Join(t.TempDir(), "migration.db")
- raw, err := sql.Open("sqlite", path)
- if err != nil {
- t.Fatal(err)
- }
- for _, statement := range migrationStatements(1) {
- if _, err := raw.ExecContext(ctx, statement); err != nil {
- t.Fatalf("create v1 schema: %v", err)
- }
- }
- if _, err := raw.ExecContext(ctx, `
- INSERT INTO admins (id, username, password_hash, created_at, updated_at)
- VALUES (1, 'legacy-admin', X'0102', 100, 100)
- `); err != nil {
- t.Fatal(err)
- }
- if _, err := raw.ExecContext(ctx, `PRAGMA user_version = 1`); err != nil {
- t.Fatal(err)
- }
- if err := raw.Close(); err != nil {
- t.Fatal(err)
- }
- database := openTestStore(t, path)
- admin, err := database.CurrentAdmin(ctx)
- if err != nil {
- t.Fatalf("legacy admin missing after migration: %v", err)
- }
- if admin.Username != "legacy-admin" || !bytes.Equal(admin.PasswordHash, []byte{1, 2}) {
- t.Fatalf("legacy admin changed during migration: %+v", admin)
- }
- var version int
- if err := database.db.QueryRowContext(ctx, `PRAGMA user_version`).Scan(&version); err != nil {
- t.Fatal(err)
- }
- if version != schemaVersion {
- t.Fatalf("schema version = %d, want %d", version, schemaVersion)
- }
- for _, table := range []string{
- "devices", "device_runtime", "vowifi_runtime", "sms_messages",
- "local_proxy_config", "upstream_proxies", "country_rules",
- "device_proxy_bindings",
- "notification_settings", "app_settings", "audit_events",
- "log_events", "card_policies", "traffic_buckets",
- } {
- var found string
- err := database.db.QueryRowContext(ctx, `
- SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?
- `, table).Scan(&found)
- if err != nil || found != table {
- t.Fatalf("migrated table %q missing: %v", table, err)
- }
- }
- }
- func TestMigration4PreservesIMSRedeliveryAndUsesReceiptTime(t *testing.T) {
- ctx := context.Background()
- path := filepath.Join(t.TempDir(), "ims-redelivery.db")
- legacy := openTestStore(t, path)
- mustSaveDevice(t, legacy, "ec20-1", "EC20")
- smscTime := time.Unix(1_700_000_000, 0).UTC()
- firstReceipt := smscTime.Add(2 * time.Hour)
- rawTPDU := "040ED0D637396C7EBBCB000062808051715140"
- for index, receivedAt := range []time.Time{firstReceipt, firstReceipt.Add(30 * time.Minute)} {
- extra, err := json.Marshal(map[string]any{"raw_tpdu": rawTPDU, "call_id": index})
- if err != nil {
- t.Fatal(err)
- }
- if _, err := legacy.SaveSMSMessage(ctx, SMSMessage{
- MessageID: fmt.Sprintf("legacy-call-%d", index),
- DeviceID: "ec20-1",
- Peer: "Vodafone",
- Direction: "inbound",
- Body: "same message",
- Timestamp: smscTime,
- Status: "received",
- Source: "ims",
- CreatedAt: receivedAt,
- Extra: extra,
- }); err != nil {
- t.Fatal(err)
- }
- }
- if _, err := legacy.db.ExecContext(ctx, `PRAGMA user_version = 3`); err != nil {
- t.Fatal(err)
- }
- if err := legacy.Close(); err != nil {
- t.Fatal(err)
- }
- migrated := openTestStore(t, path)
- messages, err := migrated.ListSMSMessages(ctx, SMSFilter{DeviceID: "ec20-1"})
- if err != nil {
- t.Fatal(err)
- }
- if len(messages) != 2 {
- t.Fatalf("message count after migration = %d, want 2", len(messages))
- }
- if !messages[0].Timestamp.Equal(firstReceipt.Add(30*time.Minute)) ||
- !messages[1].Timestamp.Equal(firstReceipt) {
- t.Fatalf("message times = %v / %v, want both receipt times", messages[0].Timestamp, messages[1].Timestamp)
- }
- var extra map[string]any
- if err := json.Unmarshal(messages[0].Extra, &extra); err != nil {
- t.Fatal(err)
- }
- if extra["service_center_timestamp_unix"] != float64(smscTime.Unix()) {
- t.Fatalf("service center time was not retained: %#v", extra)
- }
- }
- func TestDeviceStateRoundTripAndCascade(t *testing.T) {
- ctx := context.Background()
- database := openTestStore(t, ":memory:")
- rsrp, rsrq, sinr := -95, -12, 15
- attached, inserted := true, true
- mode := 1
- device := Device{
- ID: "ec20-1",
- Name: "EC20 一号",
- Interface: "wwan0",
- ControlDevice: "/dev/cdc-wdm0",
- ATPort: "/dev/ttyUSB2",
- APN: "ims",
- ProxyPort: 1080,
- QMIUseProxy: true,
- NetworkEnabled: true,
- SMSEnabled: true,
- VoWiFiEnabled: true,
- Extra: json.RawMessage(`{"slot":1}`),
- }
- runtime := DeviceRuntime{
- Running: true,
- Healthy: true,
- ControlOnline: true,
- NetworkConnected: true,
- Operator: "China Mobile",
- SignalDBM: -71,
- SignalRSRP: &rsrp,
- SignalRSRQ: &rsrq,
- SignalSINR: &sinr,
- ICCID: "8986000000000000000",
- IMSI: "460001234567890",
- PSAttached: &attached,
- SIMInserted: &inserted,
- OperatingMode: &mode,
- PhoneNumber: "+8613800138000",
- PhoneNumberSource: "cnum",
- Traffic: json.RawMessage(`{"rx":"1 MiB"}`),
- }
- vowifi := VoWiFiRuntime{
- Phase: "sms_ready",
- SIMReady: true,
- AccessReady: true,
- TunnelReady: true,
- IMSReady: true,
- SMSReady: true,
- LocalPhone: "+8613800138000",
- PhoneNumberSource: "ims",
- Tunnel: json.RawMessage(`{"ifname":"ipsec0"}`),
- }
- if err := database.SaveDeviceState(ctx, device, &runtime, &vowifi); err != nil {
- t.Fatalf("SaveDeviceState() error = %v", err)
- }
- gotDevice, err := database.Device(ctx, device.ID)
- if err != nil {
- t.Fatal(err)
- }
- if gotDevice.BaudRate != 115200 || gotDevice.DataBits != 8 ||
- gotDevice.StopBits != 1 || gotDevice.DeviceBackend != "at" {
- t.Fatalf("device defaults not applied: %+v", gotDevice)
- }
- gotRuntime, err := database.DeviceRuntime(ctx, device.ID)
- if err != nil {
- t.Fatal(err)
- }
- if gotRuntime.PhoneNumber != runtime.PhoneNumber ||
- gotRuntime.SignalRSRP == nil || *gotRuntime.SignalRSRP != rsrp ||
- gotRuntime.PSAttached == nil || !*gotRuntime.PSAttached {
- t.Fatalf("runtime did not round trip: %+v", gotRuntime)
- }
- gotVoWiFi, err := database.VoWiFiRuntime(ctx, device.ID)
- if err != nil {
- t.Fatal(err)
- }
- if !gotVoWiFi.SMSReady || gotVoWiFi.LocalPhone != vowifi.LocalPhone {
- t.Fatalf("VoWiFi runtime did not round trip: %+v", gotVoWiFi)
- }
- if err := database.DeleteDevice(ctx, device.ID); err != nil {
- t.Fatal(err)
- }
- if _, err := database.DeviceRuntime(ctx, device.ID); !errors.Is(err, ErrNotFound) {
- t.Fatalf("runtime should cascade on device deletion, got %v", err)
- }
- if _, err := database.VoWiFiRuntime(ctx, device.ID); !errors.Is(err, ErrNotFound) {
- t.Fatalf("VoWiFi runtime should cascade on device deletion, got %v", err)
- }
- }
- func TestSMSPersistenceAndDerivedThreads(t *testing.T) {
- ctx := context.Background()
- database := openTestStore(t, ":memory:")
- mustSaveDevice(t, database, "ec20-1", "客厅")
- if err := database.UpsertDeviceRuntime(ctx, DeviceRuntime{
- DeviceID: "ec20-1",
- PhoneNumber: "+8613800138000",
- }); err != nil {
- t.Fatal(err)
- }
- base := time.Unix(1_700_000_000, 0).UTC()
- if err := database.SaveSMSMessages(ctx, []SMSMessage{
- {
- MessageID: "network-1", DeviceID: "ec20-1", IMSI: "46000",
- Peer: "10086", Direction: "inbound", Body: "第一条",
- Timestamp: base, Status: "received",
- },
- {
- MessageID: "network-2", DeviceID: "ec20-1", IMSI: "46000",
- Peer: "10086", Direction: "outbound", Body: "第二条",
- Timestamp: base.Add(time.Minute), Status: "sent", Read: true,
- },
- {
- MessageID: "network-3", DeviceID: "ec20-1", IMSI: "46000",
- Peer: "95533", Direction: "received", Body: "银行提醒",
- Timestamp: base.Add(2 * time.Minute), Status: "received",
- },
- }); err != nil {
- t.Fatalf("SaveSMSMessages() error = %v", err)
- }
- // A modem retry updates the stable external id instead of duplicating it.
- if _, err := database.SaveSMSMessage(ctx, SMSMessage{
- MessageID: "network-1", DeviceID: "ec20-1", IMSI: "46000",
- Peer: "10086", Direction: "inbound", Body: "第一条(完整)",
- Timestamp: base, Status: "received",
- }); err != nil {
- t.Fatal(err)
- }
- messages, err := database.ListSMSMessages(ctx, SMSFilter{DeviceID: "ec20-1"})
- if err != nil {
- t.Fatal(err)
- }
- if len(messages) != 3 {
- t.Fatalf("message count = %d, want 3", len(messages))
- }
- if !messages[2].Timestamp.Equal(base) {
- t.Fatalf("retry changed the original message time to %v", messages[2].Timestamp)
- }
- contacts, err := database.ListSMSContacts(ctx, SMSFilter{DeviceID: "ec20-1"})
- if err != nil {
- t.Fatal(err)
- }
- if len(contacts) != 2 || contacts[0].Peer != "95533" ||
- contacts[0].UnreadCount != 1 || contacts[1].Peer != "10086" ||
- contacts[1].MessageCount != 2 || contacts[1].UnreadCount != 1 ||
- contacts[1].LocalPhone != "+8613800138000" {
- t.Fatalf("unexpected derived contacts: %+v", contacts)
- }
- marked, err := database.MarkSMSThreadRead(ctx, "ec20-1", "46000", "10086")
- if err != nil || marked != 1 {
- t.Fatalf("MarkSMSThreadRead() = %d, %v", marked, err)
- }
- contacts, err = database.ListSMSContacts(ctx, SMSFilter{Peer: "10086"})
- if err != nil {
- t.Fatal(err)
- }
- if len(contacts) != 1 || contacts[0].UnreadCount != 0 {
- t.Fatalf("thread should be read: %+v", contacts)
- }
- deleted, err := database.DeleteSMSThread(ctx, "ec20-1", "46000", "10086")
- if err != nil || deleted != 2 {
- t.Fatalf("DeleteSMSThread() = %d, %v", deleted, err)
- }
- }
- func TestListInboundSMSAfterIDUsesDurableInsertionCursor(t *testing.T) {
- ctx := context.Background()
- database := openTestStore(t, ":memory:")
- mustSaveDevice(t, database, "ec20-1", "EC20")
- old, err := database.SaveSMSMessage(ctx, SMSMessage{
- MessageID: "old-inbound", DeviceID: "ec20-1", Peer: "10086",
- Direction: "inbound", Body: "old", Status: "received",
- })
- if err != nil {
- t.Fatal(err)
- }
- if _, err := database.SaveSMSMessage(ctx, SMSMessage{
- MessageID: "new-outbound", DeviceID: "ec20-1", Peer: "10010",
- Direction: "outbound", Body: "sent", Status: "sent",
- }); err != nil {
- t.Fatal(err)
- }
- newInbound, err := database.SaveSMSMessage(ctx, SMSMessage{
- MessageID: "new-inbound", DeviceID: "ec20-1", Peer: "95533",
- Direction: "received", Body: "new", Status: "received",
- })
- if err != nil {
- t.Fatal(err)
- }
- latest, err := database.LatestSMSMessageID(ctx)
- if err != nil || latest != newInbound.ID {
- t.Fatalf("LatestSMSMessageID() = %d, %v; want %d", latest, err, newInbound.ID)
- }
- messages, err := database.ListInboundSMSAfterID(ctx, old.ID, 100)
- if err != nil {
- t.Fatal(err)
- }
- if len(messages) != 1 || messages[0].ID != newInbound.ID {
- t.Fatalf("ListInboundSMSAfterID() = %#v", messages)
- }
- }
- func TestApplySMSDeliveryReportTracksEverySubmittedPart(t *testing.T) {
- ctx := context.Background()
- database := openTestStore(t, ":memory:")
- mustSaveDevice(t, database, "ec20-1", "EC20")
- extra := json.RawMessage(`{
- "transport":"ims",
- "part_results":[{"reference":42},{"reference":43}]
- }`)
- sent, err := database.SaveSMSMessage(ctx, SMSMessage{
- MessageID: "ims-submit-1", DeviceID: "ec20-1", IMSI: "23415",
- Peer: "+447700900123", Direction: "outbound", Body: "multipart",
- Timestamp: time.Now().UTC(), Status: "accepted_by_ims", Source: "ims",
- PartsTotal: 2, DeliveryState: "accepted_by_ims", Read: true, Extra: extra,
- })
- if err != nil {
- t.Fatal(err)
- }
- first, err := database.ApplySMSDeliveryReport(ctx, SMSDeliveryReport{
- DeviceID: "ec20-1", IMSI: "23415", Peer: "+447700900123", Source: "ims",
- MessageReference: 42, StatusCode: 0, DeliveryState: "delivered",
- })
- if err != nil || first.ID != sent.ID || first.DeliveryState != "pending_delivery_report" {
- t.Fatalf("first delivery report = (%#v, %v)", first, err)
- }
- second, err := database.ApplySMSDeliveryReport(ctx, SMSDeliveryReport{
- DeviceID: "ec20-1", IMSI: "23415", Peer: "+447700900123", Source: "ims",
- MessageReference: 43, StatusCode: 0, DeliveryState: "delivered",
- })
- if err != nil || second.ID != sent.ID || second.DeliveryState != "delivered" {
- t.Fatalf("second delivery report = (%#v, %v)", second, err)
- }
- var savedExtra map[string]any
- if err := json.Unmarshal(second.Extra, &savedExtra); err != nil {
- t.Fatal(err)
- }
- reports, _ := savedExtra["delivery_reports"].(map[string]any)
- if len(reports) != 2 {
- t.Fatalf("delivery reports = %#v", reports)
- }
- }
- func TestProxyCredentialsAndCountryRules(t *testing.T) {
- ctx := context.Background()
- database := openTestStore(t, ":memory:")
- mustSaveDevice(t, database, "ec20-1", "EC20")
- if err := database.UpsertLocalProxy(ctx, LocalProxyConfig{
- ID: "local-1", Name: "SOCKS", Mode: "socks5", DeviceID: "ec20-1",
- ListenAddr: "127.0.0.1", ListenPort: 1080, Enabled: true,
- AuthEnabled: true, Username: "user", Password: "local-secret",
- }); err != nil {
- t.Fatal(err)
- }
- if err := database.UpsertLocalProxy(ctx, LocalProxyConfig{
- ID: "local-1", Name: "SOCKS 新", Mode: "socks5", DeviceID: "ec20-1",
- ListenAddr: "127.0.0.1", ListenPort: 1080, Enabled: true,
- AuthEnabled: true, Username: "user", Password: "",
- }); err != nil {
- t.Fatal(err)
- }
- local, err := database.LocalProxy(ctx, "local-1")
- if err != nil {
- t.Fatal(err)
- }
- if local.Password != "local-secret" || local.Redacted().Password != SecretMask ||
- local.Public().Password != "" {
- t.Fatalf("local proxy credential semantics failed: %+v", local)
- }
- if err := database.UpsertUpstreamProxy(ctx, UpstreamProxy{
- ID: "up-1", Name: "上游", Addr: "127.0.0.1:2080",
- Username: "up-user", Password: "up-secret", Enabled: true,
- }); err != nil {
- t.Fatal(err)
- }
- if err := database.UpsertUpstreamProxy(ctx, UpstreamProxy{
- ID: "up-1", Name: "上游新", Addr: "127.0.0.1:2080",
- Username: "up-user", Password: SecretMask, Enabled: true,
- }); err != nil {
- t.Fatal(err)
- }
- upstream, err := database.UpstreamProxy(ctx, "up-1")
- if err != nil {
- t.Fatal(err)
- }
- if upstream.Password != "up-secret" {
- t.Fatalf("blank/masked update erased upstream secret: %+v", upstream)
- }
- if got := RedactText(
- "connect local-secret through up-secret",
- local,
- upstream,
- ); strings.Contains(got, "secret") {
- t.Fatalf("RedactText leaked credentials: %q", got)
- }
- if err := database.UpsertCountryRule(ctx, CountryRule{
- CountryCode: "cn", CountryName: "中国", UpstreamProxyID: "up-1",
- Enabled: true,
- }); err != nil {
- t.Fatal(err)
- }
- rule, err := database.CountryRule(ctx, "CN")
- if err != nil || rule.CountryCode != "CN" {
- t.Fatalf("CountryRule() = %+v, %v", rule, err)
- }
- if err := database.UpsertDeviceProxyBinding(ctx, DeviceProxyBinding{
- DeviceID: "ec20-1", UpstreamProxyID: "up-1",
- }); err != nil {
- t.Fatal(err)
- }
- binding, err := database.DeviceProxyBinding(ctx, "ec20-1")
- if err != nil || binding.UpstreamProxyID != "up-1" {
- t.Fatalf("DeviceProxyBinding() = %+v, %v", binding, err)
- }
- if err := database.DeleteUpstreamProxy(ctx, "up-1"); err != nil {
- t.Fatal(err)
- }
- if _, err := database.CountryRule(ctx, "CN"); !errors.Is(err, ErrNotFound) {
- t.Fatalf("country rule should cascade with upstream deletion, got %v", err)
- }
- if _, err := database.DeviceProxyBinding(ctx, "ec20-1"); !errors.Is(err, ErrNotFound) {
- t.Fatalf("device binding should cascade with upstream deletion, got %v", err)
- }
- }
- func TestNotificationAndAppSecretPreservation(t *testing.T) {
- ctx := context.Background()
- database := openTestStore(t, ":memory:")
- if err := database.SaveNotificationSettings(ctx, []NotificationSetting{
- {
- Channel: "email",
- Config: json.RawMessage(`{"password":"mail-secret"}`),
- },
- {
- Channel: "webhook",
- Config: json.RawMessage(`not-json`),
- },
- }); err == nil {
- t.Fatal("invalid notification batch was accepted")
- }
- if _, err := database.NotificationSetting(ctx, "email"); !errors.Is(err, ErrNotFound) {
- t.Fatalf("notification batch was not rolled back: %v", err)
- }
- if err := database.UpsertNotificationSetting(ctx, NotificationSetting{
- Channel: "telegram",
- Enabled: true,
- Config: json.RawMessage(`{"bot_token":"telegram-secret","chat_id":"1"}`),
- }); err != nil {
- t.Fatal(err)
- }
- if err := database.UpsertNotificationSetting(ctx, NotificationSetting{
- Channel: "telegram",
- Enabled: true,
- Config: json.RawMessage(`{"bot_token":"","chat_id":"2"}`),
- }); err != nil {
- t.Fatal(err)
- }
- setting, err := database.NotificationSetting(ctx, "telegram")
- if err != nil {
- t.Fatal(err)
- }
- var config map[string]any
- if err := json.Unmarshal(setting.Config, &config); err != nil {
- t.Fatal(err)
- }
- if config["bot_token"] != "telegram-secret" || config["chat_id"] != "2" {
- t.Fatalf("notification merge lost data: %s", setting.Config)
- }
- if bytes.Contains(setting.Redacted().Config, []byte("telegram-secret")) ||
- bytes.Contains(setting.Public().Config, []byte("telegram-secret")) {
- t.Fatal("notification views leaked secret")
- }
- if got := RedactText("token=telegram-secret", setting); strings.Contains(got, "telegram-secret") {
- t.Fatalf("notification secret leaked in text: %q", got)
- }
- if err := database.UpsertAppSetting(ctx, AppSetting{
- Key: "provider.token", Value: json.RawMessage(`"app-secret"`), Sensitive: true,
- }); err != nil {
- t.Fatal(err)
- }
- if err := database.UpsertAppSetting(ctx, AppSetting{
- Key: "provider.token", Value: json.RawMessage(`"********"`), Sensitive: true,
- }); err != nil {
- t.Fatal(err)
- }
- appSetting, err := database.AppSetting(ctx, "provider.token")
- if err != nil {
- t.Fatal(err)
- }
- if string(appSetting.Value) != `"app-secret"` ||
- string(appSetting.Redacted().Value) != `"********"` ||
- string(appSetting.Public().Value) != `null` {
- t.Fatalf("unexpected sensitive app setting: %+v", appSetting)
- }
- }
- func TestEventsPoliciesAndTraffic(t *testing.T) {
- ctx := context.Background()
- database := openTestStore(t, ":memory:")
- old := time.Unix(1_700_000_000, 0).UTC()
- recent := old.Add(time.Hour)
- if _, err := database.AppendAuditEvent(ctx, AuditEvent{
- Actor: "admin", Action: "device.update", EntityType: "device",
- EntityID: "ec20-1", Outcome: "ok", CreatedAt: old,
- }); err != nil {
- t.Fatal(err)
- }
- if _, err := database.AppendAuditEvent(ctx, AuditEvent{
- Actor: "system", Action: "device.refresh", EntityType: "device",
- EntityID: "ec20-1", Outcome: "ok", CreatedAt: recent,
- }); err != nil {
- t.Fatal(err)
- }
- audits, err := database.ListAuditEvents(ctx, AuditFilter{Actor: "admin"})
- if err != nil || len(audits) != 1 || audits[0].Action != "device.update" {
- t.Fatalf("audit filter result = %+v, %v", audits, err)
- }
- if _, err := database.AppendLogEvent(ctx, LogEvent{
- Time: old, Level: "warn", Message: "old warning",
- Fields: json.RawMessage(`{"device":"ec20-1"}`),
- }); err != nil {
- t.Fatal(err)
- }
- if _, err := database.AppendLogEvent(ctx, LogEvent{
- Time: recent, Level: "info", Message: "ready",
- }); err != nil {
- t.Fatal(err)
- }
- logs, err := database.ListLogEvents(ctx, LogFilter{Level: "info"})
- if err != nil || len(logs) != 1 || logs[0].Message != "ready" {
- t.Fatalf("log filter result = %+v, %v", logs, err)
- }
- auditDeleted, logDeleted, err := database.PruneEvents(
- ctx,
- old.Add(time.Minute),
- old.Add(time.Minute),
- )
- if err != nil || auditDeleted != 1 || logDeleted != 1 {
- t.Fatalf("PruneEvents() = %d, %d, %v", auditDeleted, logDeleted, err)
- }
- if err := database.UpsertCardPolicy(ctx, CardPolicy{
- ICCID: "89860001", NetworkEnabled: true, VoWiFiEnabled: true,
- APN: "ims", IPVersion: "ipv4v6",
- }); err != nil {
- t.Fatal(err)
- }
- if err := database.UpsertCardPolicy(ctx, CardPolicy{
- ICCID: "invalid", VoWiFiEnabled: true, AirplaneEnabled: true,
- }); err == nil {
- t.Fatal("invalid mutually exclusive card policy was accepted")
- }
- policy, err := database.CardPolicy(ctx, "89860001")
- if err != nil || !policy.VoWiFiEnabled {
- t.Fatalf("CardPolicy() = %+v, %v", policy, err)
- }
- period := old.Truncate(time.Hour)
- if err := database.UpsertTrafficBucket(ctx, TrafficBucket{
- DeviceID: "ec20-1", Bucket: "hour", PeriodStart: period,
- RXBytes: 100, TXBytes: 25,
- }); err != nil {
- t.Fatal(err)
- }
- if err := database.AddTrafficBucket(ctx, TrafficBucket{
- DeviceID: "ec20-1", Bucket: "hour", PeriodStart: period,
- RXBytes: 5, TXBytes: 10,
- }); err != nil {
- t.Fatal(err)
- }
- buckets, err := database.ListTrafficBuckets(ctx, TrafficFilter{
- DeviceID: "ec20-1", Bucket: "hour",
- })
- if err != nil || len(buckets) != 1 ||
- buckets[0].RXBytes != 105 || buckets[0].TXBytes != 35 ||
- buckets[0].TotalBytes() != 140 {
- t.Fatalf("traffic buckets = %+v, %v", buckets, err)
- }
- }
- func openTestStore(t *testing.T, path string) *Store {
- t.Helper()
- database, err := Open(context.Background(), path)
- if err != nil {
- t.Fatalf("Open(%q) error = %v", path, err)
- }
- t.Cleanup(func() {
- if err := database.Close(); err != nil {
- t.Errorf("Close() error = %v", err)
- }
- })
- return database
- }
- func mustSaveDevice(t *testing.T, database *Store, id, name string) {
- t.Helper()
- if err := database.UpsertDevice(context.Background(), Device{
- ID: id, Name: name, SMSEnabled: true,
- }); err != nil {
- t.Fatalf("UpsertDevice() error = %v", err)
- }
- }
|