229 lines
4.8 KiB
Go
229 lines
4.8 KiB
Go
package ddns
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestNewRunner_ZeroStatus(t *testing.T) {
|
|
rn := NewRunner()
|
|
s := rn.GetStatus()
|
|
if s.Enabled {
|
|
t.Error("new runner should have Enabled=false")
|
|
}
|
|
if s.CurrentIP != "" {
|
|
t.Errorf("new runner should have empty CurrentIP, got %q", s.CurrentIP)
|
|
}
|
|
if !s.LastChecked.IsZero() {
|
|
t.Error("new runner should have zero LastChecked")
|
|
}
|
|
}
|
|
|
|
func TestTrigger_NonBlocking(t *testing.T) {
|
|
rn := NewRunner()
|
|
done := make(chan struct{})
|
|
go func() {
|
|
rn.Trigger()
|
|
rn.Trigger()
|
|
close(done)
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Error("Trigger blocked unexpectedly")
|
|
}
|
|
}
|
|
|
|
func staticParams(token string, records []string) ParamsFunc {
|
|
return func() Params {
|
|
return Params{APIToken: token, Records: records, IntervalMinutes: 5}
|
|
}
|
|
}
|
|
|
|
func TestStart_SetsEnabledStatus(t *testing.T) {
|
|
rn := NewRunner()
|
|
started := make(chan struct{}, 1)
|
|
rn.ipFetcher = func() (string, error) {
|
|
select {
|
|
case started <- struct{}{}:
|
|
default:
|
|
}
|
|
return "1.2.3.4", nil
|
|
}
|
|
|
|
rn.Start(context.Background(), staticParams("tok", []string{"a.example.com"}))
|
|
|
|
select {
|
|
case <-started:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("goroutine did not start in time")
|
|
}
|
|
|
|
if !rn.GetStatus().Enabled {
|
|
t.Error("expected Enabled=true after Start with valid params")
|
|
}
|
|
|
|
rn.Stop()
|
|
|
|
if rn.GetStatus().Enabled {
|
|
t.Error("expected Enabled=false after Stop()")
|
|
}
|
|
}
|
|
|
|
func TestStart_InvalidParams_SetsDisabled(t *testing.T) {
|
|
for _, pfn := range []ParamsFunc{
|
|
func() Params { return Params{} },
|
|
func() Params { return Params{APIToken: ""} },
|
|
func() Params { return Params{APIToken: "tok", Records: nil} },
|
|
} {
|
|
rn := NewRunner()
|
|
rn.Start(context.Background(), pfn)
|
|
if rn.GetStatus().Enabled {
|
|
t.Errorf("expected Enabled=false for empty params")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStart_Restart_KeepsEnabled(t *testing.T) {
|
|
rn := NewRunner()
|
|
reached := make(chan struct{}, 2)
|
|
rn.ipFetcher = func() (string, error) {
|
|
select {
|
|
case reached <- struct{}{}:
|
|
default:
|
|
}
|
|
return "1.2.3.4", nil
|
|
}
|
|
|
|
pfn := staticParams("tok", []string{"a.example.com"})
|
|
rn.Start(context.Background(), pfn)
|
|
|
|
select {
|
|
case <-reached:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("first goroutine did not start")
|
|
}
|
|
|
|
// Restart — old goroutine is cancelled, new one starts
|
|
rn.Start(context.Background(), pfn)
|
|
|
|
if !rn.GetStatus().Enabled {
|
|
t.Error("expected Enabled=true after restart")
|
|
}
|
|
|
|
rn.Stop()
|
|
}
|
|
|
|
func TestStop_ClearsEnabled(t *testing.T) {
|
|
rn := NewRunner()
|
|
rn.mu.Lock()
|
|
rn.status.Enabled = true
|
|
rn.mu.Unlock()
|
|
|
|
rn.Stop()
|
|
|
|
if rn.GetStatus().Enabled {
|
|
t.Error("expected Enabled=false after Stop()")
|
|
}
|
|
}
|
|
|
|
func TestStart_CancelsAndRestarts(t *testing.T) {
|
|
rn := NewRunner()
|
|
|
|
block := make(chan struct{})
|
|
firstReady := make(chan struct{}, 1)
|
|
rn.ipFetcher = func() (string, error) {
|
|
select {
|
|
case firstReady <- struct{}{}:
|
|
default:
|
|
}
|
|
<-block
|
|
return "1.2.3.4", nil
|
|
}
|
|
|
|
pfn := staticParams("tok", []string{"a.example.com"})
|
|
rn.Start(context.Background(), pfn)
|
|
|
|
select {
|
|
case <-firstReady:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("first goroutine did not start")
|
|
}
|
|
|
|
close(block)
|
|
|
|
// Second Start with disabled params must not deadlock
|
|
done := make(chan struct{})
|
|
go func() {
|
|
rn.Start(context.Background(), func() Params { return Params{} })
|
|
close(done)
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Error("second Start blocked unexpectedly")
|
|
}
|
|
}
|
|
|
|
func TestCheckAndUpdate_UpdatesCurrentIP(t *testing.T) {
|
|
rn := NewRunner()
|
|
rn.ipFetcher = func() (string, error) { return "5.6.7.8", nil }
|
|
rn.paramsFn = staticParams("fake", []string{"a.example.com"})
|
|
|
|
rn.checkAndUpdate()
|
|
|
|
if rn.GetStatus().CurrentIP != "5.6.7.8" {
|
|
t.Errorf("expected CurrentIP=5.6.7.8, got %q", rn.GetStatus().CurrentIP)
|
|
}
|
|
}
|
|
|
|
func TestCheckAndUpdate_SkipsWhenNoParams(t *testing.T) {
|
|
rn := NewRunner()
|
|
rn.ipFetcher = func() (string, error) { return "1.2.3.4", nil }
|
|
rn.paramsFn = func() Params { return Params{} }
|
|
|
|
rn.checkAndUpdate()
|
|
|
|
if rn.GetStatus().CurrentIP != "" {
|
|
t.Error("expected empty CurrentIP when params have no token/records")
|
|
}
|
|
}
|
|
|
|
func TestCheckAndUpdate_ParamsFuncCalledEachTime(t *testing.T) {
|
|
rn := NewRunner()
|
|
rn.ipFetcher = func() (string, error) { return "1.2.3.4", nil }
|
|
|
|
calls := 0
|
|
rn.paramsFn = func() Params {
|
|
calls++
|
|
return Params{APIToken: "fake", Records: []string{"a.example.com"}}
|
|
}
|
|
|
|
rn.checkAndUpdate()
|
|
rn.checkAndUpdate()
|
|
|
|
if calls != 2 {
|
|
t.Errorf("expected paramsFn called 2 times, got %d", calls)
|
|
}
|
|
}
|
|
|
|
func TestGetStatus_ReflectsManualUpdate(t *testing.T) {
|
|
rn := NewRunner()
|
|
rn.mu.Lock()
|
|
rn.status.CurrentIP = "1.2.3.4"
|
|
rn.status.LastChecked = time.Now()
|
|
rn.status.Enabled = true
|
|
rn.mu.Unlock()
|
|
|
|
s := rn.GetStatus()
|
|
if s.CurrentIP != "1.2.3.4" {
|
|
t.Errorf("got CurrentIP %q, want 1.2.3.4", s.CurrentIP)
|
|
}
|
|
if !s.Enabled {
|
|
t.Error("expected Enabled=true")
|
|
}
|
|
}
|