Files
VoCat/cmd/vocat/main_test.go
T
MengMengCode 8a260e86f1 Implement automatic task management with CRUD operations and UI integration
- Added `automatic_tasks.go` and `automatic_tasks_test.go` for backend logic and testing of automatic tasks.
- Created `automatic_tasks_test.go` to validate task claiming and deletion behavior.
- Developed `AutomaticTasksPage.tsx` for frontend management of automatic tasks, including task creation, editing, and execution.
- Integrated device and eSIM profile selection for task configuration.
- Implemented automatic task scheduling and retry logic in the backend.
2026-08-10 22:15:54 +08:00

220 lines
6.7 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"io"
"log/slog"
"testing"
"time"
"vocat/internal/device"
"vocat/internal/modem"
"vocat/internal/store"
)
// fakeModemClient is a minimal scripted modem.Client for exercising the region
// enforcement orchestration (flight-mode flips) without hardware.
type fakeModemClient struct {
steps []fakeStep
index int
}
type fakeStep struct {
command string
lines []string
}
func (client *fakeModemClient) Execute(_ context.Context, command string) (modem.Response, error) {
if client.index >= len(client.steps) {
return modem.Response{}, fmt.Errorf("unexpected command %q", command)
}
step := client.steps[client.index]
client.index++
if command != step.command {
return modem.Response{}, fmt.Errorf("command %q, want %q", command, step.command)
}
return modem.Response{Command: command, Lines: step.lines, Final: "OK"}, nil
}
func (client *fakeModemClient) WaitURC(context.Context, func(string) bool) (string, error) {
return "", errors.New("no URC scripted")
}
func (client *fakeModemClient) Close() error { return nil }
func (client *fakeModemClient) assertExhausted(t *testing.T) {
t.Helper()
if client.index != len(client.steps) {
t.Fatalf("consumed %d of %d scripted commands", client.index, len(client.steps))
}
}
type fakeDiscoverer struct{ candidates []modem.Candidate }
func (discoverer fakeDiscoverer) Discover(context.Context) ([]modem.Candidate, error) {
return discoverer.candidates, nil
}
type fakeOpener struct{ client modem.Client }
func (opener fakeOpener) Open(context.Context, modem.Port) (modem.Client, error) {
return opener.client, nil
}
const regionTestDeviceID = "quectel-region-test"
func newRegionTestManager(t *testing.T, client modem.Client) *device.Manager {
t.Helper()
manager, err := device.NewManager(device.Options{
Discoverer: fakeDiscoverer{candidates: []modem.Candidate{{
ID: regionTestDeviceID,
Product: "EC20",
ATPort: modem.Port{Path: "/dev/ttyUSB2", Role: modem.PortRoleAT},
}}},
Opener: fakeOpener{client: client},
CommandTimeout: time.Second,
LongTimeout: time.Second,
})
if err != nil {
t.Fatalf("NewManager: %v", err)
}
if err := manager.Start(context.Background()); err != nil {
t.Fatalf("Start: %v", err)
}
t.Cleanup(func() { _ = manager.Stop(context.Background()) })
return manager
}
func newRegionTestStore(t *testing.T) *store.Store {
t.Helper()
database, err := store.Open(context.Background(), ":memory:")
if err != nil {
t.Fatalf("store.Open: %v", err)
}
t.Cleanup(func() { _ = database.Close() })
return database
}
func regionTestLogger() *slog.Logger {
return slog.New(slog.NewTextHandler(io.Discard, nil))
}
func TestEnforceCardRegionForcesAirplaneAndPersistsPolicy(t *testing.T) {
client := &fakeModemClient{steps: []fakeStep{
{command: "AT+CFUN?", lines: []string{"+CFUN: 1"}},
{command: "AT+CFUN=4"},
{command: "AT+CFUN?", lines: []string{"+CFUN: 4"}},
}}
manager := newRegionTestManager(t, client)
database := newRegionTestStore(t)
snapshot := &device.Snapshot{
DeviceID: regionTestDeviceID,
SIMReady: true,
IMSI: "460001234567890",
ICCID: "89860012345678901234",
}
enforceCardRegion(context.Background(), regionTestLogger(), database, manager, regionTestDeviceID, snapshot)
client.assertExhausted(t)
policy, err := database.CardPolicy(context.Background(), snapshot.ICCID)
if err != nil {
t.Fatalf("CardPolicy: %v", err)
}
if policy.Source != cardPolicySourceRegionBlock {
t.Fatalf("policy source = %q, want %q", policy.Source, cardPolicySourceRegionBlock)
}
if policy.NetworkEnabled || policy.VoWiFiEnabled || !policy.AirplaneEnabled {
t.Fatalf("policy switches = %#v, want all service off and airplane on", policy)
}
}
func TestEnforceCardRegionSkipsRadioWhenAlreadyOff(t *testing.T) {
client := &fakeModemClient{}
manager := newRegionTestManager(t, client)
database := newRegionTestStore(t)
snapshot := &device.Snapshot{
DeviceID: regionTestDeviceID,
SIMReady: true,
IMSI: "461001234567890",
ICCID: "89860012345678901234",
FlightMode: true,
}
enforceCardRegion(context.Background(), regionTestLogger(), database, manager, regionTestDeviceID, snapshot)
client.assertExhausted(t)
if _, err := database.CardPolicy(context.Background(), snapshot.ICCID); err != nil {
t.Fatalf("expected a persisted block policy even with the radio already off: %v", err)
}
}
func TestEnforceCardRegionLiftsBlockForAllowedSIM(t *testing.T) {
client := &fakeModemClient{}
manager := newRegionTestManager(t, client)
database := newRegionTestStore(t)
if err := database.UpsertCardPolicy(context.Background(), store.CardPolicy{
ICCID: "89860012345678901234",
AirplaneEnabled: true,
IPVersion: "IPV4V6",
Source: cardPolicySourceRegionBlock,
}); err != nil {
t.Fatalf("seed block policy: %v", err)
}
snapshot := &device.Snapshot{
DeviceID: regionTestDeviceID,
SIMReady: true,
IMSI: "310260123456789",
ICCID: "89012601234567890123",
FlightMode: true,
}
enforceCardRegion(context.Background(), regionTestLogger(), database, manager, regionTestDeviceID, snapshot)
client.assertExhausted(t)
if _, err := database.CardPolicy(context.Background(), "89860012345678901234"); !errors.Is(err, store.ErrNotFound) {
t.Fatalf("expected the auto block policy to be cleared, got err=%v", err)
}
}
func TestEnforceCardRegionLeavesAllowedSIMWithoutPriorBlockAlone(t *testing.T) {
client := &fakeModemClient{}
manager := newRegionTestManager(t, client)
database := newRegionTestStore(t)
snapshot := &device.Snapshot{
DeviceID: regionTestDeviceID,
SIMReady: true,
IMSI: "310260123456789",
ICCID: "89012601234567890123",
}
enforceCardRegion(context.Background(), regionTestLogger(), database, manager, regionTestDeviceID, snapshot)
client.assertExhausted(t)
}
func TestEnforceCardRegionIgnoresUnknownOrNotReadySIM(t *testing.T) {
client := &fakeModemClient{}
manager := newRegionTestManager(t, client)
database := newRegionTestStore(t)
// Not ready: no action at all.
notReady := &device.Snapshot{DeviceID: regionTestDeviceID, SIMReady: false, IMSI: "460001234567890"}
enforceCardRegion(context.Background(), regionTestLogger(), database, manager, regionTestDeviceID, notReady)
// Ready but IMSI unknown: hold state, neither block nor lift.
unknown := &device.Snapshot{DeviceID: regionTestDeviceID, SIMReady: true, IMSI: ""}
enforceCardRegion(context.Background(), regionTestLogger(), database, manager, regionTestDeviceID, unknown)
client.assertExhausted(t)
policies, err := database.ListCardPolicies(context.Background())
if err != nil {
t.Fatalf("ListCardPolicies: %v", err)
}
if len(policies) != 0 {
t.Fatalf("expected no card policies, got %d", len(policies))
}
}