Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

165 рядки
5.0KB

  1. package control
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "net/http"
  7. "net/http/httptest"
  8. "testing"
  9. cfgpkg "github.com/jan/fm-rds-tx/internal/config"
  10. )
  11. func TestHealthz(t *testing.T) {
  12. srv := NewServer(cfgpkg.Default())
  13. rec := httptest.NewRecorder()
  14. srv.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/healthz", nil))
  15. if rec.Code != 200 {
  16. t.Fatalf("status: %d", rec.Code)
  17. }
  18. }
  19. func TestStatus(t *testing.T) {
  20. srv := NewServer(cfgpkg.Default())
  21. rec := httptest.NewRecorder()
  22. srv.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/status", nil))
  23. if rec.Code != 200 {
  24. t.Fatalf("status: %d", rec.Code)
  25. }
  26. var body map[string]any
  27. json.Unmarshal(rec.Body.Bytes(), &body)
  28. if body["service"] != "fm-rds-tx" {
  29. t.Fatal("missing service")
  30. }
  31. if _, ok := body["preEmphasisTauUS"]; !ok {
  32. t.Fatal("missing preEmphasisTauUS")
  33. }
  34. }
  35. func TestStatusReportsRuntimeIndicator(t *testing.T) {
  36. srv := NewServer(cfgpkg.Default())
  37. srv.SetTXController(&fakeTXController{stats: map[string]any{"runtimeIndicator": "degraded", "runtimeAlert": "late buffers"}})
  38. rec := httptest.NewRecorder()
  39. srv.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/status", nil))
  40. if rec.Code != 200 {
  41. t.Fatalf("status: %d", rec.Code)
  42. }
  43. var body map[string]any
  44. json.Unmarshal(rec.Body.Bytes(), &body)
  45. if body["runtimeIndicator"] != "degraded" {
  46. t.Fatalf("expected runtimeIndicator degraded, got %v", body["runtimeIndicator"])
  47. }
  48. if body["runtimeAlert"] != "late buffers" {
  49. t.Fatalf("expected runtimeAlert late buffers, got %v", body["runtimeAlert"])
  50. }
  51. }
  52. func TestDryRunEndpoint(t *testing.T) {
  53. srv := NewServer(cfgpkg.Default())
  54. rec := httptest.NewRecorder()
  55. srv.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/dry-run", nil))
  56. if rec.Code != 200 {
  57. t.Fatalf("status: %d", rec.Code)
  58. }
  59. var body map[string]any
  60. json.Unmarshal(rec.Body.Bytes(), &body)
  61. if body["mode"] != "dry-run" {
  62. t.Fatal("wrong mode")
  63. }
  64. }
  65. func TestConfigPatch(t *testing.T) {
  66. srv := NewServer(cfgpkg.Default())
  67. body := []byte(`{"toneLeftHz":900,"radioText":"hello world","preEmphasisTauUS":75}`)
  68. rec := httptest.NewRecorder()
  69. srv.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/config", bytes.NewReader(body)))
  70. if rec.Code != 200 {
  71. t.Fatalf("status: %d body=%s", rec.Code, rec.Body.String())
  72. }
  73. }
  74. func TestRuntimeWithoutDriver(t *testing.T) {
  75. srv := NewServer(cfgpkg.Default())
  76. rec := httptest.NewRecorder()
  77. srv.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/runtime", nil))
  78. if rec.Code != 200 {
  79. t.Fatalf("status: %d", rec.Code)
  80. }
  81. }
  82. func TestTXStartWithoutController(t *testing.T) {
  83. srv := NewServer(cfgpkg.Default())
  84. rec := httptest.NewRecorder()
  85. srv.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/tx/start", nil))
  86. if rec.Code != http.StatusServiceUnavailable {
  87. t.Fatalf("expected 503, got %d", rec.Code)
  88. }
  89. }
  90. func TestConfigPatchUpdatesSnapshot(t *testing.T) {
  91. srv := NewServer(cfgpkg.Default())
  92. srv.SetTXController(&fakeTXController{})
  93. rec := httptest.NewRecorder()
  94. body := []byte(`{"outputDrive":1.2}`)
  95. srv.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/config", bytes.NewReader(body)))
  96. if rec.Code != 200 {
  97. t.Fatalf("status: %d", rec.Code)
  98. }
  99. var resp map[string]any
  100. if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
  101. t.Fatalf("unmarshal response: %v", err)
  102. }
  103. if live, ok := resp["live"].(bool); !ok || !live {
  104. t.Fatalf("expected live true, got %v", resp["live"])
  105. }
  106. rec = httptest.NewRecorder()
  107. srv.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/config", nil))
  108. var cfg cfgpkg.Config
  109. if err := json.NewDecoder(rec.Body).Decode(&cfg); err != nil {
  110. t.Fatalf("decode config: %v", err)
  111. }
  112. if cfg.FM.OutputDrive != 1.2 {
  113. t.Fatalf("expected snapshot to reflect new drive, got %v", cfg.FM.OutputDrive)
  114. }
  115. }
  116. func TestConfigPatchEngineRejectsDoesNotUpdateSnapshot(t *testing.T) {
  117. srv := NewServer(cfgpkg.Default())
  118. srv.SetTXController(&fakeTXController{updateErr: errors.New("boom")})
  119. body := []byte(`{"outputDrive":2.2}`)
  120. rec := httptest.NewRecorder()
  121. srv.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/config", bytes.NewReader(body)))
  122. if rec.Code != http.StatusBadRequest {
  123. t.Fatalf("expected 400, got %d", rec.Code)
  124. }
  125. rec = httptest.NewRecorder()
  126. srv.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/config", nil))
  127. var cfg cfgpkg.Config
  128. if err := json.NewDecoder(rec.Body).Decode(&cfg); err != nil {
  129. t.Fatalf("decode config: %v", err)
  130. }
  131. if cfg.FM.OutputDrive != cfgpkg.Default().FM.OutputDrive {
  132. t.Fatalf("expected snapshot untouched, got %v", cfg.FM.OutputDrive)
  133. }
  134. }
  135. type fakeTXController struct {
  136. updateErr error
  137. stats map[string]any
  138. }
  139. func (f *fakeTXController) StartTX() error { return nil }
  140. func (f *fakeTXController) StopTX() error { return nil }
  141. func (f *fakeTXController) TXStats() map[string]any {
  142. if f.stats != nil {
  143. return f.stats
  144. }
  145. return map[string]any{}
  146. }
  147. func (f *fakeTXController) UpdateConfig(_ LivePatch) error { return f.updateErr }