testutils

package
v1.2.40 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 17, 2026 License: AGPL-3.0 Imports: 43 Imported by: 0

Documentation

Overview

SPDX-License-Identifier: AGPL-3.0-or-later DMRHub - Run a DMR network server in a single binary Copyright (C) 2023-2026 Jacob McSwain

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.

The source code is available at <https://github.com/USA-RedDragon/DMRHub>

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrIPSCHandshakeTimeout  = errors.New("IPSC handshake timed out")
	ErrIPSCDeregisterTimeout = errors.New("timed out waiting for IPSC deregistration")
	ErrIPSCNotConnected      = errors.New("IPSC client not connected")
)
View Source
var ErrMMDVMHandshakeTimeout = errors.New("MMDVM handshake timed out")
View Source
var ErrOpenBridgeNotConnected = errors.New("OpenBridge client not connected")

Functions

func CreateAndLoginUser added in v1.0.15

func CreateAndLoginUser(t *testing.T, router *gin.Engine, user apimodels.UserRegistration) (APIResponse, *httptest.ResponseRecorder, CookieJar)

func CreateScheduledNet added in v1.2.36

CreateScheduledNet creates a scheduled net via the API.

func DeleteScheduledNet added in v1.2.36

func DeleteScheduledNet(t *testing.T, router *gin.Engine, jar CookieJar, id uint) *httptest.ResponseRecorder

DeleteScheduledNet deletes a scheduled net via the API.

func ExportNetCheckIns added in v1.2.36

func ExportNetCheckIns(t *testing.T, router *gin.Engine, jar CookieJar, netID uint, format string) *httptest.ResponseRecorder

ExportNetCheckIns exports check-ins for a net via the API.

func GetNet added in v1.2.36

func GetNet(t *testing.T, router *gin.Engine, jar CookieJar, netID uint) (apimodels.NetResponse, *httptest.ResponseRecorder)

GetNet retrieves a net by ID via the API.

func GetNetCheckIns added in v1.2.36

func GetNetCheckIns(t *testing.T, router *gin.Engine, jar CookieJar, netID uint) (*httptest.ResponseRecorder, []byte)

GetNetCheckIns retrieves check-ins for a net via the API.

func GetPeer added in v1.2.33

func GetPeer(t *testing.T, router *gin.Engine, id uint, jar CookieJar) (models.Peer, *httptest.ResponseRecorder)

func GetRepeater added in v1.2.33

func GetRepeater(t *testing.T, router *gin.Engine, id uint, jar CookieJar) (models.Repeater, *httptest.ResponseRecorder)

func GetTalkgroup added in v1.2.33

func GetTalkgroup(t *testing.T, router *gin.Engine, id uint, jar CookieJar) (models.Talkgroup, *httptest.ResponseRecorder)

func GetUser added in v1.0.15

func GetUser(t *testing.T, router *gin.Engine, dmrID uint, jar CookieJar) (models.User, *httptest.ResponseRecorder)

func GetUserMe added in v1.0.15

func GetUserMe(t *testing.T, router *gin.Engine, jar CookieJar) (models.User, *httptest.ResponseRecorder)

func ListNets added in v1.2.36

func ListNets(t *testing.T, router *gin.Engine, jar CookieJar, queryParams string) (*httptest.ResponseRecorder, []byte)

ListNets lists nets via the API.

func LoginAdmin added in v1.0.15

func LoginAdmin(t *testing.T, router *gin.Engine) (APIResponse, *httptest.ResponseRecorder, CookieJar)

func LoginUser added in v1.0.15

func PatchNet added in v1.2.38

func PatchNet(t *testing.T, router *gin.Engine, jar CookieJar, netID uint, body apimodels.NetPatch) (apimodels.NetResponse, *httptest.ResponseRecorder)

PatchNet updates a net (e.g. showcase toggle) via the API.

func PatchPeer added in v1.2.36

func PatchPeer(t *testing.T, router *gin.Engine, id uint, jar CookieJar, patch apimodels.PeerPatch) (models.Peer, *httptest.ResponseRecorder)

func StartNet added in v1.2.36

StartNet starts a new net via the API.

func StopNet added in v1.2.36

func StopNet(t *testing.T, router *gin.Engine, jar CookieJar, netID uint) (apimodels.NetResponse, *httptest.ResponseRecorder)

StopNet stops an active net via the API.

Types

type APIResponse

type APIResponse struct {
	Message string `json:"message"`
	Error   string `json:"error"`
}

func ApproveUser added in v1.0.15

func ApproveUser(t *testing.T, router *gin.Engine, dmrID uint, jar CookieJar) (APIResponse, *httptest.ResponseRecorder)

func CreateTalkgroup added in v1.2.33

func CreateTalkgroup(t *testing.T, router *gin.Engine, jar CookieJar, tg apimodels.TalkgroupPost) (APIResponse, *httptest.ResponseRecorder)

func DeletePeer added in v1.2.33

func DeletePeer(t *testing.T, router *gin.Engine, id uint, jar CookieJar) (APIResponse, *httptest.ResponseRecorder)

func DeletePeerRule added in v1.2.36

func DeletePeerRule(t *testing.T, router *gin.Engine, peerID uint, ruleID uint, jar CookieJar) (APIResponse, *httptest.ResponseRecorder)

func DeleteRepeater added in v1.2.33

func DeleteRepeater(t *testing.T, router *gin.Engine, id uint, jar CookieJar) (APIResponse, *httptest.ResponseRecorder)

func DeleteTalkgroup added in v1.2.33

func DeleteTalkgroup(t *testing.T, router *gin.Engine, id uint, jar CookieJar) (APIResponse, *httptest.ResponseRecorder)

func DeleteUser added in v1.0.15

func DeleteUser(t *testing.T, router *gin.Engine, dmrID uint, jar CookieJar) (APIResponse, *httptest.ResponseRecorder)

func DemoteUser added in v1.0.15

func DemoteUser(t *testing.T, router *gin.Engine, dmrID uint, jar CookieJar) (APIResponse, *httptest.ResponseRecorder)

func LinkRepeater added in v1.2.33

func LinkRepeater(t *testing.T, router *gin.Engine, id uint, linkType string, slot string, target uint, jar CookieJar) (APIResponse, *httptest.ResponseRecorder)

func LogoutUser added in v1.0.15

func LogoutUser(t *testing.T, router *gin.Engine, jar CookieJar) (APIResponse, *httptest.ResponseRecorder)

func PatchRepeater added in v1.2.33

func PatchRepeater(t *testing.T, router *gin.Engine, id uint, jar CookieJar, patch apimodels.RepeaterPatch) (APIResponse, *httptest.ResponseRecorder)

func PatchTalkgroup added in v1.2.33

func PatchTalkgroup(t *testing.T, router *gin.Engine, id uint, jar CookieJar, patch apimodels.TalkgroupPatch) (APIResponse, *httptest.ResponseRecorder)

func PatchUser added in v1.2.33

func PatchUser(t *testing.T, router *gin.Engine, dmrID uint, jar CookieJar, patch apimodels.UserPatch) (APIResponse, *httptest.ResponseRecorder)

func PromoteUser added in v1.0.15

func PromoteUser(t *testing.T, router *gin.Engine, dmrID uint, jar CookieJar) (APIResponse, *httptest.ResponseRecorder)

func RegisterUser added in v1.0.15

func SetRepeaterTalkgroups added in v1.2.33

func SetRepeaterTalkgroups(t *testing.T, router *gin.Engine, id uint, jar CookieJar, talkgroups apimodels.RepeaterTalkgroupsPost) (APIResponse, *httptest.ResponseRecorder)

func SetTalkgroupAdmins added in v1.2.33

func SetTalkgroupAdmins(t *testing.T, router *gin.Engine, id uint, jar CookieJar, action apimodels.TalkgroupAdminAction) (APIResponse, *httptest.ResponseRecorder)

func SetTalkgroupNCOs added in v1.2.33

func SetTalkgroupNCOs(t *testing.T, router *gin.Engine, id uint, jar CookieJar, action apimodels.TalkgroupAdminAction) (APIResponse, *httptest.ResponseRecorder)

func SuspendUser added in v1.0.15

func SuspendUser(t *testing.T, router *gin.Engine, dmrID uint, jar CookieJar) (APIResponse, *httptest.ResponseRecorder)

func UnlinkRepeater added in v1.2.33

func UnlinkRepeater(t *testing.T, router *gin.Engine, id uint, linkType string, slot string, target uint, jar CookieJar) (APIResponse, *httptest.ResponseRecorder)

func UnsuspendUser added in v1.0.15

func UnsuspendUser(t *testing.T, router *gin.Engine, dmrID uint, jar CookieJar) (APIResponse, *httptest.ResponseRecorder)

type APIResponseUserList added in v1.0.15

type APIResponseUserList struct {
	Total int           `json:"total"`
	Users []models.User `json:"users"`
}

func ListUsers added in v1.0.15

func ListUsers(t *testing.T, router *gin.Engine, jar CookieJar) (APIResponseUserList, *httptest.ResponseRecorder)

type Backend added in v1.2.25

type Backend struct {
	Name  string
	Setup func(t *testing.T, cfg *config.Config)
}

Backend describes a database/cache backend configuration for integration tests.

func PostgresRedisBackend added in v1.2.25

func PostgresRedisBackend() Backend

PostgresRedisBackend returns a backend that spins up a dedicated Postgres and Redis container for each test. Containers are fully parallel.

func SQLiteMemoryBackend added in v1.2.25

func SQLiteMemoryBackend() Backend

SQLiteMemoryBackend returns a backend using in-memory SQLite and in-memory pubsub/KV.

type CookieJar added in v1.0.15

type CookieJar struct {
	// contains filtered or unexported fields
}

func (*CookieJar) Cookies added in v1.0.15

func (t *CookieJar) Cookies() []http.Cookie

func (*CookieJar) SetCookies added in v1.0.15

func (t *CookieJar) SetCookies(cookies []http.Cookie)

type IPSCBurst added in v1.2.25

type IPSCBurst struct {
	PacketType byte
	PeerID     uint32
	Src        uint
	Dst        uint
	GroupCall  bool
	Slot       bool
	Data       []byte
}

IPSCBurst represents a raw IPSC user packet received by the simulator client.

type IPSCClient added in v1.2.25

type IPSCClient struct {
	PeerID   uint32
	AuthKey  []byte // raw HMAC-SHA1 key (20 bytes, from decodeAuthKey)
	Password string // hex auth key string
	// contains filtered or unexported fields
}

IPSCClient is a simulated IPSC peer client for integration testing. It performs the full IPSC registration handshake (MasterRegisterRequest → MasterRegisterReply) and can send/receive IPSC voice burst packets over UDP.

func NewIPSCClient added in v1.2.25

func NewIPSCClient(peerID uint32, password string) *IPSCClient

NewIPSCClient creates a new IPSC simulator client. password is the hex auth key string as stored in the database.

func (*IPSCClient) Close added in v1.2.25

func (c *IPSCClient) Close()

Close shuts down the client.

func (*IPSCClient) Connect added in v1.2.25

func (c *IPSCClient) Connect(addr string) error

Connect dials the IPSC server and starts the registration handshake.

func (*IPSCClient) ConnectWithoutRegistration added in v1.2.29

func (c *IPSCClient) ConnectWithoutRegistration(addr string) error

ConnectWithoutRegistration dials the IPSC server but does NOT send a MasterRegisterRequest. The client can still send HMAC-signed packets (e.g. voice data) through the UDP socket. This is useful for testing that the server rejects traffic from peers that have not completed the registration handshake.

func (*IPSCClient) Drain added in v1.2.25

func (c *IPSCClient) Drain(timeout time.Duration) []IPSCBurst

Drain collects all packets received within the given timeout window.

func (*IPSCClient) ReceivedPackets added in v1.2.25

func (c *IPSCClient) ReceivedPackets() <-chan IPSCBurst

ReceivedPackets returns a channel of IPSC bursts received from the server.

func (*IPSCClient) SendGroupVoice added in v1.2.25

func (c *IPSCClient) SendGroupVoice(src, dst uint, slot bool, streamID uint32) error

SendGroupVoice sends an IPSC group voice burst with a voice LC header, a voice burst, and a terminator.

func (*IPSCClient) SendPrivateVoice added in v1.2.25

func (c *IPSCClient) SendPrivateVoice(src, dst uint, slot bool, streamID uint32) error

SendPrivateVoice sends an IPSC private voice burst.

func (*IPSCClient) WaitDisconnected added in v1.2.29

func (c *IPSCClient) WaitDisconnected(timeout time.Duration) error

WaitDisconnected blocks until the client receives a DeregistrationRequest from the server.

func (*IPSCClient) WaitReady added in v1.2.25

func (c *IPSCClient) WaitReady(timeout time.Duration) error

WaitReady blocks until the registration handshake is complete.

func (*IPSCClient) WaitReconnected added in v1.2.29

func (c *IPSCClient) WaitReconnected(timeout time.Duration) error

WaitReconnected blocks until the client has completed a re-handshake after a deregistration disconnect. Returns ErrIPSCHandshakeTimeout if the re-handshake does not complete within the timeout.

type IntegrationStack added in v1.2.25

type IntegrationStack struct {
	Config           *config.Config
	DB               *gorm.DB
	Hub              *hub.Hub
	PubSub           pubsub.PubSub
	KV               kv.KV
	CallTracker      *calltracker.CallTracker
	MMDVMServer      *mmdvm.Server
	IPSCServer       *ipsc.IPSCServer
	OpenBridgeServer *openbridge.Server
	MMDVMAddr        string // host:port of the MMDVM server
	IPSCAddr         string // host:port of the IPSC server
	OpenBridgeAddr   string // host:port of the OpenBridge server
}

IntegrationStack is a full DMRHub test environment with real MMDVM and IPSC servers on ephemeral UDP ports, backed by in-memory SQLite, pubsub, and KV.

func SetupIntegrationStack added in v1.2.25

func SetupIntegrationStack(t *testing.T, backends ...Backend) *IntegrationStack

SetupIntegrationStack creates and starts a full integration test environment. The hub is NOT started yet — call SeedAndStart() after seeding your DB data.

func (*IntegrationStack) AssignTS1StaticTG added in v1.2.25

func (s *IntegrationStack) AssignTS1StaticTG(t *testing.T, repeaterID, tgID uint)

AssignTS1StaticTG adds a talkgroup to a repeater's TS1 static list.

func (*IntegrationStack) AssignTS2StaticTG added in v1.2.25

func (s *IntegrationStack) AssignTS2StaticTG(t *testing.T, repeaterID, tgID uint)

AssignTS2StaticTG adds a talkgroup to a repeater's TS2 static list.

func (*IntegrationStack) SeedEndedNet added in v1.2.36

func (s *IntegrationStack) SeedEndedNet(t *testing.T, talkgroupID, startedByUserID uint, description string, duration time.Duration) models.Net

SeedEndedNet creates an ended net in the database for integration testing.

func (*IntegrationStack) SeedIPSCRepeater added in v1.2.25

func (s *IntegrationStack) SeedIPSCRepeater(t *testing.T, id uint, ownerID uint, password string)

SeedIPSCRepeater creates an IPSC repeater in the DB.

func (*IntegrationStack) SeedMMDVMRepeater added in v1.2.25

func (s *IntegrationStack) SeedMMDVMRepeater(t *testing.T, id uint, ownerID uint, password string)

SeedMMDVMRepeater creates an MMDVM repeater in the DB.

func (*IntegrationStack) SeedNet added in v1.2.36

func (s *IntegrationStack) SeedNet(t *testing.T, talkgroupID, startedByUserID uint, description string) models.Net

SeedNet creates an active net in the database for integration testing.

func (*IntegrationStack) SeedPeer added in v1.2.36

func (s *IntegrationStack) SeedPeer(t *testing.T, id uint, ownerID uint, password string, ip string, port int, ingress bool, egress bool)

SeedPeer creates an OpenBridge peer in the DB.

func (*IntegrationStack) SeedPeerRule added in v1.2.36

func (s *IntegrationStack) SeedPeerRule(t *testing.T, peerID uint, direction bool, subjectIDMin uint, subjectIDMax uint)

SeedPeerRule creates a peer rule in the DB.

func (*IntegrationStack) SeedScheduledNet added in v1.2.36

func (s *IntegrationStack) SeedScheduledNet(t *testing.T, talkgroupID, createdByUserID uint, name string, dayOfWeek int, timeOfDay, timezone string) models.ScheduledNet

SeedScheduledNet creates a scheduled net in the database for integration testing.

func (*IntegrationStack) SeedTalkgroup added in v1.2.25

func (s *IntegrationStack) SeedTalkgroup(t *testing.T, id uint, name string)

SeedTalkgroup creates a Talkgroup in the DB.

func (*IntegrationStack) SeedUser added in v1.2.25

func (s *IntegrationStack) SeedUser(t *testing.T, id uint, callsign string)

SeedUser creates a User in the DB.

func (*IntegrationStack) SpawnSecondReplica added in v1.2.26

func (s *IntegrationStack) SpawnSecondReplica(t *testing.T) string

SpawnSecondReplica creates a second Hub + MMDVM server that shares the same DB, pubsub, and KV as the original stack — simulating two replicas of the app. The second replica's Hub is started; repeater subscriptions are activated when repeaters connect via the MMDVM handshake. Returns the second MMDVM server's listen address.

func (*IntegrationStack) StartServers added in v1.2.25

func (s *IntegrationStack) StartServers(t *testing.T)

StartServers starts the Hub and the MMDVM, IPSC, and OpenBridge servers. Repeater subscriptions are activated lazily when repeaters connect via the protocol handshake.

type MMDVMClient added in v1.2.25

type MMDVMClient struct {
	RepeaterID uint32
	Callsign   string
	Password   string
	// contains filtered or unexported fields
}

MMDVMClient is a simulated MMDVM repeater client for integration testing. It performs the full Homebrew protocol handshake (RPTL → RPTACK → RPTK → RPTACK → RPTC → RPTACK) and can send/receive DMRD voice packets over UDP.

func NewMMDVMClient added in v1.2.25

func NewMMDVMClient(repeaterID uint32, callsign, password string) *MMDVMClient

NewMMDVMClient creates a new MMDVM simulator client.

func (*MMDVMClient) Close added in v1.2.25

func (c *MMDVMClient) Close()

Close shuts down the client.

func (*MMDVMClient) Connect added in v1.2.25

func (c *MMDVMClient) Connect(addr string) error

Connect dials the MMDVM server and starts the handshake.

func (*MMDVMClient) Drain added in v1.2.25

func (c *MMDVMClient) Drain(timeout time.Duration) []models.Packet

Drain collects all packets received within the given timeout window. Useful for negative assertions (proving nothing extra was delivered).

func (*MMDVMClient) ReceivedPackets added in v1.2.25

func (c *MMDVMClient) ReceivedPackets() <-chan models.Packet

ReceivedPackets returns a channel of DMRD packets received from the server.

func (*MMDVMClient) SendDMRD added in v1.2.25

func (c *MMDVMClient) SendDMRD(pkt models.Packet) error

SendDMRD sends a raw DMRD voice packet to the server.

func (*MMDVMClient) WaitReady added in v1.2.25

func (c *MMDVMClient) WaitReady(timeout time.Duration) error

WaitReady blocks until the handshake is complete or the timeout expires.

type OpenBridgeClient added in v1.2.36

type OpenBridgeClient struct {
	PeerID   uint32
	Password string
	// contains filtered or unexported fields
}

OpenBridgeClient is a simulated OpenBridge peer client for integration testing. OpenBridge is stateless (no handshake) — packets are authenticated via HMAC-SHA1.

func NewOpenBridgeClient added in v1.2.36

func NewOpenBridgeClient(peerID uint32, password string) *OpenBridgeClient

NewOpenBridgeClient creates a new OpenBridge simulator client.

func (*OpenBridgeClient) Close added in v1.2.36

func (c *OpenBridgeClient) Close()

Close shuts down the client.

func (*OpenBridgeClient) Connect added in v1.2.36

func (c *OpenBridgeClient) Connect(addr string) error

Connect dials the OpenBridge server. No handshake is needed.

func (*OpenBridgeClient) Drain added in v1.2.36

func (c *OpenBridgeClient) Drain(timeout time.Duration) []models.Packet

Drain collects all packets received within the given timeout window.

func (*OpenBridgeClient) LocalAddr added in v1.2.36

func (c *OpenBridgeClient) LocalAddr() *net.UDPAddr

LocalAddr returns the local UDP address of the client. Useful for configuring the peer's IP/Port in the DB for outbound delivery.

func (*OpenBridgeClient) ReceivedPackets added in v1.2.36

func (c *OpenBridgeClient) ReceivedPackets() <-chan models.Packet

ReceivedPackets returns a channel of packets received from the server.

func (*OpenBridgeClient) SendGroupVoice added in v1.2.36

func (c *OpenBridgeClient) SendGroupVoice(src, dst uint, streamID uint32) error

SendGroupVoice sends an OpenBridge group voice packet (voice head + voice + terminator).

func (*OpenBridgeClient) SendRawBytes added in v1.2.36

func (c *OpenBridgeClient) SendRawBytes(data []byte) error

SendRawBytes sends raw bytes over the UDP connection (no HMAC added). Useful for testing invalid HMAC scenarios.

func (*OpenBridgeClient) SendRawPacket added in v1.2.36

func (c *OpenBridgeClient) SendRawPacket(pkt models.Packet) error

SendRawPacket sends a single DMRD packet with HMAC appended.

type PeerCreateResponse added in v1.2.33

type PeerCreateResponse struct {
	Message  string `json:"message"`
	Password string `json:"password"`
	Error    string `json:"error"`
}

func CreatePeer added in v1.2.33

type PeerRuleCreateResponse added in v1.2.36

type PeerRuleCreateResponse struct {
	Message string          `json:"message"`
	Rule    models.PeerRule `json:"rule"`
	Error   string          `json:"error"`
}

func CreatePeerRule added in v1.2.36

func CreatePeerRule(t *testing.T, router *gin.Engine, peerID uint, jar CookieJar, rule apimodels.PeerRulePost) (PeerRuleCreateResponse, *httptest.ResponseRecorder)

type PeerRulesResponse added in v1.2.36

type PeerRulesResponse struct {
	Rules []models.PeerRule `json:"rules"`
	Error string            `json:"error"`
}

func GetPeerRules added in v1.2.36

func GetPeerRules(t *testing.T, router *gin.Engine, peerID uint, jar CookieJar) (PeerRulesResponse, *httptest.ResponseRecorder)

type RepeaterCreateResponse added in v1.2.33

type RepeaterCreateResponse struct {
	Message  string `json:"message"`
	Password string `json:"password"`
	Error    string `json:"error"`
}

func CreateRepeater added in v1.2.33

func CreateRepeater(t *testing.T, router *gin.Engine, jar CookieJar, repeater apimodels.RepeaterPost) (RepeaterCreateResponse, *httptest.ResponseRecorder)

type TestDB

type TestDB struct {
	// contains filtered or unexported fields
}

func CreateTestDBRouter

func CreateTestDBRouter() (*gin.Engine, *TestDB, error)

func CreateTestDBRouterWithHub added in v1.2.33

func CreateTestDBRouterWithHub() (*gin.Engine, *TestDB, error)

func (*TestDB) CloseDB

func (t *TestDB) CloseDB()

func (*TestDB) DB added in v1.2.27

func (t *TestDB) DB() *gorm.DB

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL