Compare commits
4 Commits
41eb06d247
...
main
Author | SHA1 | Date | |
---|---|---|---|
cd17d3ae7f | |||
12ef7dbd73 | |||
aaf7994324 | |||
afa36b85cf |
88
README.md
88
README.md
@@ -6,23 +6,42 @@ A Go client and utility for interacting with the IamResponding (IaR) API, design
|
|||||||
- API client for IaR with keep-alive and alerting support
|
- API client for IaR with keep-alive and alerting support
|
||||||
- Configurable via YAML and Go structs
|
- Configurable via YAML and Go structs
|
||||||
- Modular codebase for easy extension
|
- Modular codebase for easy extension
|
||||||
- Logging with slog
|
- Comprehensive test coverage
|
||||||
|
- File system watcher for MP3 files
|
||||||
|
- Event-driven architecture with custom event bus
|
||||||
|
- Structured logging with slog
|
||||||
|
- HTTP client with automatic cookie management
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
```
|
```
|
||||||
config.yml # Example configuration file
|
config.yml # Example configuration file
|
||||||
|
go.mod # Go module file
|
||||||
|
go.sum # Go dependencies checksum
|
||||||
main.go # Entry point
|
main.go # Entry point
|
||||||
bus/ # Event bus logic
|
bus/
|
||||||
client/ # IaR API client and helpers
|
├── bus.go # Custom event bus implementation
|
||||||
config/ # Configuration structs and loading
|
└── bus_test.go # Event bus tests
|
||||||
utils/ # Utility functions
|
client/
|
||||||
watcher/ # File or event watcher logic
|
├── client.go # IaR API client
|
||||||
|
├── response.go # API response structures
|
||||||
|
├── request.go # API request structures
|
||||||
|
├── client_test.go # Basic client tests
|
||||||
|
└── client_api_test.go # API integration tests with mocked responses
|
||||||
|
config/
|
||||||
|
├── config.go # Configuration loading and structs
|
||||||
|
└── config_test.go # Configuration tests
|
||||||
|
utils/
|
||||||
|
├── encoder.go # MP3 to base64 encoder
|
||||||
|
└── encoder_test.go # Encoder tests
|
||||||
|
watcher/
|
||||||
|
├── watcher.go # File system watcher for MP3 files
|
||||||
|
└── watcher_test.go # Watcher tests
|
||||||
```
|
```
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
- Go 1.20 or newer
|
- Go 1.25 or newer
|
||||||
- Access to the IamResponding API (credentials required)
|
- Access to the IamResponding API (credentials required)
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
@@ -38,7 +57,24 @@ go mod tidy
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
Edit `config.yml` with your IaR credentials and settings.
|
Create a `config.yml` file with your IaR credentials and settings:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
credentials:
|
||||||
|
secret_key: "your_secret_key"
|
||||||
|
ttd_api_key: "your_ttd_api_key"
|
||||||
|
directory: "/path/to/watch/for/mp3/files"
|
||||||
|
watch_debounce_seconds: 5
|
||||||
|
logging:
|
||||||
|
level: "INFO"
|
||||||
|
```
|
||||||
|
|
||||||
|
Configuration options:
|
||||||
|
- `credentials.secret_key`: IaR API secret key
|
||||||
|
- `credentials.ttd_api_key`: IaR API key
|
||||||
|
- `directory`: Directory to watch for MP3 files
|
||||||
|
- `watch_debounce_seconds`: Debounce time for file events (default: 1)
|
||||||
|
- `logging.level`: Log level (DEBUG, INFO, WARN, ERROR)
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
Build and run the main application:
|
Build and run the main application:
|
||||||
@@ -52,12 +88,48 @@ go build -o iar-notificator main.go
|
|||||||
./iar-notificator
|
./iar-notificator
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The application will:
|
||||||
|
1. Load configuration from `config.yml`
|
||||||
|
2. Start watching the specified directory for MP3 files
|
||||||
|
3. Send keep-alive signals to IaR API at regular intervals
|
||||||
|
4. Automatically upload MP3 files as alerts when detected
|
||||||
|
|
||||||
|
## API Integration
|
||||||
|
The client integrates with the IaR API using GraphQL queries and mutations:
|
||||||
|
|
||||||
|
- **KeepAlive**: Retrieves pager group information and maintains connection
|
||||||
|
- **PreAlert**: Creates an alert entry
|
||||||
|
- **Alert**: Uploads audio data to an existing alert
|
||||||
|
|
||||||
|
API responses are parsed and used to populate internal state for subsequent requests.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
Run all tests:
|
Run all tests:
|
||||||
```sh
|
```sh
|
||||||
go test ./...
|
go test ./...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Run tests for specific packages:
|
||||||
|
```sh
|
||||||
|
go test ./client
|
||||||
|
go test ./config
|
||||||
|
go test ./bus
|
||||||
|
go test ./watcher
|
||||||
|
go test ./utils
|
||||||
|
```
|
||||||
|
|
||||||
|
The test suite includes:
|
||||||
|
- Unit tests for all core functionality
|
||||||
|
- Integration tests with mocked HTTP responses
|
||||||
|
- File system watcher tests
|
||||||
|
- Configuration loading tests
|
||||||
|
- Event bus concurrency tests
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- `resty.dev/v3`: HTTP client for API requests
|
||||||
|
- `github.com/fsnotify/fsnotify`: File system watching
|
||||||
|
- `gopkg.in/yaml.v3`: YAML configuration parsing
|
||||||
|
|
||||||
## License
|
## License
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
|
@@ -41,6 +41,7 @@ type IaR struct {
|
|||||||
PagerGroupID string
|
PagerGroupID string
|
||||||
PagerGroupName string
|
PagerGroupName string
|
||||||
Type string
|
Type string
|
||||||
|
KeepAlive time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// New function creates a New MySubaru API client
|
// New function creates a New MySubaru API client
|
||||||
@@ -52,6 +53,7 @@ func New(credentials *config.Credentials, logger *slog.Logger) (*Client, error)
|
|||||||
PagerGroupID: "",
|
PagerGroupID: "",
|
||||||
PagerGroupName: "",
|
PagerGroupName: "",
|
||||||
Type: "",
|
Type: "",
|
||||||
|
KeepAlive: 15 * time.Minute,
|
||||||
},
|
},
|
||||||
isAlive: false,
|
isAlive: false,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
@@ -86,6 +88,7 @@ func (c *Client) KeepAlive() error {
|
|||||||
c.IAR.PagerGroupID = pg.ID
|
c.IAR.PagerGroupID = pg.ID
|
||||||
c.IAR.PagerGroupName = pg.Name
|
c.IAR.PagerGroupName = pg.Name
|
||||||
c.IAR.Type = pg.Type
|
c.IAR.Type = pg.Type
|
||||||
|
c.IAR.KeepAlive = time.Duration(resp.Data.GetTTDSetting.KeyValue) * time.Second
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -118,11 +121,11 @@ func (c *Client) Alert(audioBase64 string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ServeKeepAlive starts a goroutine that sends keep-alive requests at the specified interval.
|
// ServeKeepAlive starts a goroutine that sends keep-alive requests at the specified interval.
|
||||||
func (c *Client) ServeKeepAlive(ctx context.Context, interval time.Duration) {
|
func (c *Client) ServeKeepAlive(ctx context.Context) {
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(c.IAR.KeepAlive)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
slog.Info("Starting keep-alive sender", "interval", interval)
|
slog.Info("Starting keep-alive sender", slog.Duration("interval", c.IAR.KeepAlive))
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
@@ -1,179 +0,0 @@
|
|||||||
package client_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.savin.nyc/alex/go-iar-notificator/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
const keepAliveResponse = `{
|
|
||||||
"data": {
|
|
||||||
"getMutualPagerGroupsList": [
|
|
||||||
{
|
|
||||||
"_id": "672783715f6ed07deb857afb",
|
|
||||||
"subscriberId": 536873008,
|
|
||||||
"name": "NPFD - 672783715f6ed07deb857afb",
|
|
||||||
"type": "ab tone",
|
|
||||||
"tone_tolerance": 0.02,
|
|
||||||
"gaplength": 0,
|
|
||||||
"ignore_after": 60,
|
|
||||||
"record_delay": 2,
|
|
||||||
"record_seconds": 15,
|
|
||||||
"release_time": 3,
|
|
||||||
"playback_during_record": 0,
|
|
||||||
"post_email_command": "",
|
|
||||||
"alert_command": "",
|
|
||||||
"atone": 1656.4,
|
|
||||||
"btone": 656.1,
|
|
||||||
"atonelength": 0.9,
|
|
||||||
"btonelength": 3,
|
|
||||||
"longtone": null,
|
|
||||||
"longtonelength": null,
|
|
||||||
"createDate": "2024-11-03T14:06:41.765Z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"getTTDSetting": {
|
|
||||||
"keySetting": "pollingSeconds",
|
|
||||||
"keyValue": "900"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
const preAlertResponse = `{
|
|
||||||
"data": {
|
|
||||||
"addAlert": {
|
|
||||||
"_id": "68bb90e0b91373858dd8f214",
|
|
||||||
"textAlert": "NPFD Received at 21:39:44 09/05/2025",
|
|
||||||
"pagerGroup": "NPFD",
|
|
||||||
"subscriberId": 536873008
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
const alertResponse = `{
|
|
||||||
"data": {
|
|
||||||
"addAlert": {
|
|
||||||
"_id": "68bb90e0b91373858dd8f214",
|
|
||||||
"textAlert": "NPFD Received at 21:40:05 09/05/2025",
|
|
||||||
"pagerGroup": "NPFD",
|
|
||||||
"audioUrl": "https://storage.iamresponding.com/v3/xzPAzU4ZEXlRtMLtj2NlquvalB26xQfS.mp3",
|
|
||||||
"subscriberId": 536873008
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
// patchClientHTTP replaces the HTTPClient's transport with a test server
|
|
||||||
func patchClientHTTP(c *client.Client, handler http.HandlerFunc) {
|
|
||||||
ts := httptest.NewServer(handler)
|
|
||||||
c.HTTPClient.SetBaseURL(ts.URL)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClient_KeepAlive_Success(t *testing.T) {
|
|
||||||
logger := mockLogger()
|
|
||||||
creds := mockCredentials()
|
|
||||||
c, err := client.New(creds, logger)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("New() error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
patchClientHTTP(c, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte(keepAliveResponse))
|
|
||||||
})
|
|
||||||
|
|
||||||
err = c.KeepAlive()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("KeepAlive() error = %v, want nil", err)
|
|
||||||
}
|
|
||||||
if !c.IsAlive() {
|
|
||||||
t.Error("expected client to be alive after successful KeepAlive")
|
|
||||||
}
|
|
||||||
// Check if IAR is set
|
|
||||||
if c.IAR.PagerGroupID != "672783715f6ed07deb857afb" {
|
|
||||||
t.Errorf("expected PagerGroupID to be set, got %s", c.IAR.PagerGroupID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClient_KeepAlive_Failure(t *testing.T) {
|
|
||||||
logger := mockLogger()
|
|
||||||
creds := mockCredentials()
|
|
||||||
c, err := client.New(creds, logger)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("New() error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
patchClientHTTP(c, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte(`{"error": "fail"}`))
|
|
||||||
})
|
|
||||||
|
|
||||||
err = c.KeepAlive()
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error from KeepAlive, got nil")
|
|
||||||
}
|
|
||||||
if c.IsAlive() {
|
|
||||||
t.Error("expected client to not be alive after failed KeepAlive")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClient_PreAlert_Success(t *testing.T) {
|
|
||||||
logger := mockLogger()
|
|
||||||
creds := mockCredentials()
|
|
||||||
c, err := client.New(creds, logger)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("New() error: %v", err)
|
|
||||||
}
|
|
||||||
// Set IAR
|
|
||||||
c.IAR.PagerGroupID = "test_group"
|
|
||||||
|
|
||||||
patchClientHTTP(c, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte(preAlertResponse))
|
|
||||||
})
|
|
||||||
|
|
||||||
alertID, err := c.PreAlert()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("PreAlert() error = %v, want nil", err)
|
|
||||||
}
|
|
||||||
expectedID := "68bb90e0b91373858dd8f214"
|
|
||||||
if alertID != expectedID {
|
|
||||||
t.Errorf("expected alertID %s, got %s", expectedID, alertID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClient_Alert_Success(t *testing.T) {
|
|
||||||
logger := mockLogger()
|
|
||||||
creds := mockCredentials()
|
|
||||||
c, err := client.New(creds, logger)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("New() error: %v", err)
|
|
||||||
}
|
|
||||||
// Set IAR
|
|
||||||
c.IAR.PagerGroupID = "test_group"
|
|
||||||
|
|
||||||
callCount := 0
|
|
||||||
patchClientHTTP(c, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
if callCount == 0 {
|
|
||||||
// First call is preAlert
|
|
||||||
w.Write([]byte(preAlertResponse))
|
|
||||||
} else {
|
|
||||||
// Second call is Alert
|
|
||||||
w.Write([]byte(alertResponse))
|
|
||||||
}
|
|
||||||
callCount++
|
|
||||||
})
|
|
||||||
|
|
||||||
err = c.Alert("base64_audio")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Alert() error = %v, want nil", err)
|
|
||||||
}
|
|
||||||
if callCount != 2 {
|
|
||||||
t.Errorf("expected 2 HTTP calls, got %d", callCount)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,6 +13,62 @@ import (
|
|||||||
"git.savin.nyc/alex/go-iar-notificator/config"
|
"git.savin.nyc/alex/go-iar-notificator/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const keepAliveResponse = `{
|
||||||
|
"data": {
|
||||||
|
"getMutualPagerGroupsList": [
|
||||||
|
{
|
||||||
|
"_id": "672783715f6ed07deb857afb",
|
||||||
|
"subscriberId": 536873008,
|
||||||
|
"name": "NPFD - 672783715f6ed07deb857afb",
|
||||||
|
"type": "ab tone",
|
||||||
|
"tone_tolerance": 0.02,
|
||||||
|
"gaplength": 0,
|
||||||
|
"ignore_after": 60,
|
||||||
|
"record_delay": 2,
|
||||||
|
"record_seconds": 15,
|
||||||
|
"release_time": 3,
|
||||||
|
"playback_during_record": 0,
|
||||||
|
"post_email_command": "",
|
||||||
|
"alert_command": "",
|
||||||
|
"atone": 1656.4,
|
||||||
|
"btone": 656.1,
|
||||||
|
"atonelength": 0.9,
|
||||||
|
"btonelength": 3,
|
||||||
|
"longtone": null,
|
||||||
|
"longtonelength": null,
|
||||||
|
"createDate": "2024-11-03T14:06:41.765Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"getTTDSetting": {
|
||||||
|
"keySetting": "pollingSeconds",
|
||||||
|
"keyValue": "900"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const preAlertResponse = `{
|
||||||
|
"data": {
|
||||||
|
"addAlert": {
|
||||||
|
"_id": "68bb90e0b91373858dd8f214",
|
||||||
|
"textAlert": "NPFD Received at 21:39:44 09/05/2025",
|
||||||
|
"pagerGroup": "NPFD",
|
||||||
|
"subscriberId": 536873008
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const alertResponse = `{
|
||||||
|
"data": {
|
||||||
|
"addAlert": {
|
||||||
|
"_id": "68bb90e0b91373858dd8f214",
|
||||||
|
"textAlert": "NPFD Received at 21:40:05 09/05/2025",
|
||||||
|
"pagerGroup": "NPFD",
|
||||||
|
"audioUrl": "https://storage.iamresponding.com/v3/xzPAzU4ZEXlRtMLtj2NlquvalB26xQfS.mp3",
|
||||||
|
"subscriberId": 536873008
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
// mockLogger returns a no-op slog.Logger for testing
|
// mockLogger returns a no-op slog.Logger for testing
|
||||||
func mockLogger() *slog.Logger {
|
func mockLogger() *slog.Logger {
|
||||||
return slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{}))
|
return slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{}))
|
||||||
@@ -50,9 +108,128 @@ func TestServeKeepAliveCancel(t *testing.T) {
|
|||||||
creds := mockCredentials()
|
creds := mockCredentials()
|
||||||
c, _ := client.New(creds, logger)
|
c, _ := client.New(creds, logger)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
go c.ServeKeepAlive(ctx, 10*time.Millisecond)
|
go c.ServeKeepAlive(ctx)
|
||||||
// Let it run briefly
|
// Let it run briefly
|
||||||
time.Sleep(20 * time.Millisecond)
|
time.Sleep(20 * time.Millisecond)
|
||||||
cancel()
|
cancel()
|
||||||
// No panic or deadlock expected
|
// No panic or deadlock expected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// patchClientHTTP replaces the HTTPClient's transport with a test server
|
||||||
|
func patchClientHTTP(c *client.Client, handler http.HandlerFunc) {
|
||||||
|
ts := httptest.NewServer(handler)
|
||||||
|
c.HTTPClient.SetBaseURL(ts.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_KeepAlive_Success(t *testing.T) {
|
||||||
|
logger := mockLogger()
|
||||||
|
creds := mockCredentials()
|
||||||
|
c, err := client.New(creds, logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
patchClientHTTP(c, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(keepAliveResponse))
|
||||||
|
})
|
||||||
|
|
||||||
|
err = c.KeepAlive()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("KeepAlive() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
if !c.IsAlive() {
|
||||||
|
t.Error("expected client to be alive after successful KeepAlive")
|
||||||
|
}
|
||||||
|
// Check if IAR is set
|
||||||
|
if c.IAR.PagerGroupID != "672783715f6ed07deb857afb" {
|
||||||
|
t.Errorf("expected PagerGroupID to be set, got %s", c.IAR.PagerGroupID)
|
||||||
|
}
|
||||||
|
// Check if KeepAlive duration is set from API response (900 seconds = 15 minutes)
|
||||||
|
expectedDuration := 900 * time.Second
|
||||||
|
if c.IAR.KeepAlive != expectedDuration {
|
||||||
|
t.Errorf("expected KeepAlive duration to be %v, got %v", expectedDuration, c.IAR.KeepAlive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_KeepAlive_Failure(t *testing.T) {
|
||||||
|
logger := mockLogger()
|
||||||
|
creds := mockCredentials()
|
||||||
|
c, err := client.New(creds, logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
patchClientHTTP(c, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(`{"error": "fail"}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
err = c.KeepAlive()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error from KeepAlive, got nil")
|
||||||
|
}
|
||||||
|
if c.IsAlive() {
|
||||||
|
t.Error("expected client to not be alive after failed KeepAlive")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_PreAlert_Success(t *testing.T) {
|
||||||
|
logger := mockLogger()
|
||||||
|
creds := mockCredentials()
|
||||||
|
c, err := client.New(creds, logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New() error: %v", err)
|
||||||
|
}
|
||||||
|
// Set IAR
|
||||||
|
c.IAR.PagerGroupID = "test_group"
|
||||||
|
|
||||||
|
patchClientHTTP(c, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(preAlertResponse))
|
||||||
|
})
|
||||||
|
|
||||||
|
alertID, err := c.PreAlert()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("PreAlert() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
expectedID := "68bb90e0b91373858dd8f214"
|
||||||
|
if alertID != expectedID {
|
||||||
|
t.Errorf("expected alertID %s, got %s", expectedID, alertID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_Alert_Success(t *testing.T) {
|
||||||
|
logger := mockLogger()
|
||||||
|
creds := mockCredentials()
|
||||||
|
c, err := client.New(creds, logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New() error: %v", err)
|
||||||
|
}
|
||||||
|
// Set IAR
|
||||||
|
c.IAR.PagerGroupID = "test_group"
|
||||||
|
|
||||||
|
callCount := 0
|
||||||
|
patchClientHTTP(c, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
if callCount == 0 {
|
||||||
|
// First call is preAlert
|
||||||
|
w.Write([]byte(preAlertResponse))
|
||||||
|
} else {
|
||||||
|
// Second call is Alert
|
||||||
|
w.Write([]byte(alertResponse))
|
||||||
|
}
|
||||||
|
callCount++
|
||||||
|
})
|
||||||
|
|
||||||
|
err = c.Alert("base64_audio")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Alert() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
if callCount != 2 {
|
||||||
|
t.Errorf("expected 2 HTTP calls, got %d", callCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -30,7 +30,7 @@ type PagerGroup struct {
|
|||||||
|
|
||||||
type TTDSetting struct {
|
type TTDSetting struct {
|
||||||
KeySetting string `json:"keySetting"`
|
KeySetting string `json:"keySetting"`
|
||||||
KeyValue string `json:"keyValue"`
|
KeyValue int `json:"keyValue,string"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddAlert struct {
|
type AddAlert struct {
|
||||||
|
6
main.go
6
main.go
@@ -70,14 +70,14 @@ func main() {
|
|||||||
// Start directory watcher in a goroutine
|
// Start directory watcher in a goroutine
|
||||||
go watcher.WatchDirectory(ctx, config.Directory, eb, logger)
|
go watcher.WatchDirectory(ctx, config.Directory, eb, logger)
|
||||||
|
|
||||||
// Start keep-alive sender in a goroutine
|
|
||||||
go iar.ServeKeepAlive(ctx, 15*time.Minute)
|
|
||||||
|
|
||||||
err = iar.KeepAlive()
|
err = iar.KeepAlive()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Initial keep-alive failed", "error", err)
|
slog.Error("Initial keep-alive failed", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start keep-alive sender in a goroutine
|
||||||
|
go iar.ServeKeepAlive(ctx)
|
||||||
|
|
||||||
// Handle OS signals for shutdown
|
// Handle OS signals for shutdown
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
Reference in New Issue
Block a user