123 lines
4.8 KiB
Go
123 lines
4.8 KiB
Go
package monthlyusage
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
|
"github.com/gogf/gf/v2/database/gdb"
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/dao"
|
|
)
|
|
|
|
// SQLite runs the SAME predicate and ordering with only the two MySQL hints
|
|
// removed. This checks full eligibility/TaskID parity, not MySQL access plans.
|
|
// Production EXPLAIN must still validate the unmodified channelPageSQL.
|
|
type sqliteChannelScanner struct{ DatabaseScanner }
|
|
|
|
func (s *sqliteChannelScanner) ScanChannel(ctx context.Context, key string, last int64, size int) ([]Card, error) {
|
|
query := strings.ReplaceAll(channelPageSQL, "FORCE INDEX (idx_card_chan_key)", "")
|
|
query = strings.ReplaceAll(query, "STRAIGHT_JOIN", "INNER JOIN")
|
|
var rows []struct {
|
|
ID int64 `orm:"id"`
|
|
ICCID string `orm:"iccid"`
|
|
MSISDN string `orm:"msisdn"`
|
|
IMSI string `orm:"imsi"`
|
|
ChanKey string `orm:"chan_key"`
|
|
}
|
|
if err := dao.TSimCardInfo.DB().Raw(query, key, last, size).Ctx(ctx).Scan(&rows); err != nil {
|
|
return nil, err
|
|
}
|
|
cards := make([]Card, 0, len(rows))
|
|
for _, row := range rows {
|
|
cards = append(cards, Card{ID: row.ID, ICCID: row.ICCID, MSISDN: row.MSISDN, IMSI: row.IMSI, ChanKey: row.ChanKey})
|
|
}
|
|
return cards, nil
|
|
}
|
|
|
|
func TestDatabaseQualificationAndCompleteTaskIDParity(t *testing.T) {
|
|
// No application DB/config: isolated SQLite file and loopback Redis only.
|
|
if err := gdb.SetConfig(gdb.Config{"default": gdb.ConfigGroup{{Type: "sqlite", Link: "sqlite::@file(" + t.TempDir() + "/cards.db)"}}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx := contextForTest(t)
|
|
db := dao.TSimCardInfo.DB()
|
|
t.Cleanup(func() { _ = db.Close(context.Background()) })
|
|
for _, sql := range []string{
|
|
`CREATE TABLE sys_dept (dept_id INTEGER PRIMARY KEY, tenant_id TEXT, deleted_at DATETIME)`,
|
|
`CREATE TABLE t_sim_card_info (id INTEGER PRIMARY KEY, iccid TEXT, msisdn TEXT, imsi TEXT, chan_key TEXT, current_package_code TEXT, dept_id INTEGER, tenant_id TEXT)`,
|
|
`CREATE INDEX idx_card_chan_key ON t_sim_card_info(chan_key)`,
|
|
`CREATE TABLE t_sim_channels (chan_key TEXT, temp_key TEXT, sync_enabled INTEGER, operator INTEGER)`,
|
|
`INSERT INTO sys_dept VALUES (1,'t1',NULL),(2,'t2',NULL),(3,'t1','2026-01-01')`,
|
|
`INSERT INTO t_sim_channels VALUES ('a','sim_yd_v5',0,1),('b','sim_dx_v1',0,2),('disabled','sim_yd_v5',1,1),('no-template','',0,1)`,
|
|
`INSERT INTO t_sim_card_info VALUES
|
|
(1,'i1','m1','s1','a','pkg',1,'t1'),
|
|
(2,'i2','m2','s2','b','pkg',2,'t2'),
|
|
(3,'i3','','','a','pkg',1,'t1'),
|
|
(4,'','','','a','pkg',1,'t1'),
|
|
(5,'i5','','','a','',1,'t1'),
|
|
(6,'i6','','','a','pkg',1,'wrong-tenant'),
|
|
(7,'i7','','','a','pkg',3,'t1'),
|
|
(8,'i8','','','b','pkg',99,'t2'),
|
|
(9,'i9','','','disabled','pkg',1,'t1'),
|
|
(10,'i10','','','no-template','pkg',1,'t1'),
|
|
(11,'i11','','','unknown','pkg',1,'t1'),
|
|
(12,'i12','','','','pkg',1,'t1'),
|
|
(13,'i13','','','b','pkg',2,'t2')`,
|
|
} {
|
|
if _, err := db.Exec(ctx, sql); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
store := testStore(t)
|
|
for _, scope := range [][]string{nil, {"a"}, {"b", "disabled", "no-template", "unknown"}, {"unknown"}} {
|
|
var sets [][]string
|
|
for _, mode := range []string{"serial", "channel"} {
|
|
scanner := &sqliteChannelScanner{}
|
|
p := testProducer(t, scanner, store, Config{PageSize: 2})
|
|
run := "db-" + mode
|
|
if err := p.Produce(ctx, scopedMessage(run, scope...)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ids := taskIDs(t, store, run)
|
|
for i, id := range ids {
|
|
ids[i] = strings.TrimPrefix(id, run+":")
|
|
}
|
|
sort.Strings(ids)
|
|
sets = append(sets, ids)
|
|
stateKey, _ := store.TaskStateKey(run)
|
|
metaKey, _ := store.RunMetaKey(run)
|
|
chanKey, _ := store.RunChannelsKey(run)
|
|
_, _ = store.RedisClient().Do(ctx, "DEL", stateKey, metaKey, chanKey)
|
|
for _, key := range []string{"a", "b"} {
|
|
activeKey, _ := store.RoundActiveKey(run, 1, key)
|
|
_, _ = store.RedisClient().Do(ctx, "DEL", activeKey)
|
|
}
|
|
_, _ = store.RedisClient().Do(ctx, "SREM", store.ActiveRunsKey(), run)
|
|
}
|
|
if !reflect.DeepEqual(sets[0], sets[1]) {
|
|
t.Fatalf("scope=%v serial=%v channel=%v", scope, sets[0], sets[1])
|
|
}
|
|
if scope == nil {
|
|
want := []string{operation + ":1", operation + ":13", operation + ":2", operation + ":3"}
|
|
sort.Strings(want)
|
|
if !reflect.DeepEqual(sets[0], want) {
|
|
t.Fatalf("qualification set=%v want=%v", sets[0], want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestChannelSQLShapeAndParameters(t *testing.T) {
|
|
for _, term := range []string{"FORCE INDEX (idx_card_chan_key)", "STRAIGHT_JOIN sys_dept", "c.chan_key = ?", "c.id > ?", "c.iccid <> ''", "c.current_package_code <> ''", "d.deleted_at IS NULL", "c.tenant_id = d.tenant_id", "ORDER BY c.id ASC", "LIMIT ?"} {
|
|
if !strings.Contains(channelPageSQL, term) {
|
|
t.Fatalf("missing %q", term)
|
|
}
|
|
}
|
|
if strings.Contains(channelPageSQL, "<=") || strings.Count(channelPageSQL, "?") != 3 {
|
|
t.Fatal("SQL contains upper bound or changed bind parameter count")
|
|
}
|
|
}
|