Files
the-other-dude/poller/internal/device/normalize_test.go
Jason Staack f1abb75cab feat(02-01): add SSH executor with TOFU host key verification and config normalizer
- SSH RunCommand with typed error classification (auth, hostkey, timeout, connection refused, truncated)
- TOFU host key callback: accept-on-first-connect, verify-on-subsequent, reject-on-mismatch
- NormalizeConfig strips timestamps, normalizes line endings, trims whitespace, collapses blanks
- HashConfig returns 64-char lowercase hex SHA256 of normalized config
- 22 unit tests covering all error kinds, TOFU flows, normalization edge cases, idempotency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 20:46:04 -05:00

117 lines
3.4 KiB
Go

package device
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNormalizeConfig_StripTimestampHeader(t *testing.T) {
input := "# 2024/01/15 10:30:00 by RouterOS 7.14\n# software id = ABC123\n/ip address\n"
got := NormalizeConfig(input)
assert.NotContains(t, got, "2024/01/15")
assert.Contains(t, got, "# software id = ABC123")
assert.Contains(t, got, "/ip address")
}
func TestNormalizeConfig_LineEndingNormalization(t *testing.T) {
input := "/ip address\r\nadd address=10.0.0.1\r\n"
got := NormalizeConfig(input)
assert.NotContains(t, got, "\r")
assert.Contains(t, got, "/ip address\n")
}
func TestNormalizeConfig_TrailingWhitespaceTrimming(t *testing.T) {
input := " /ip address \n"
got := NormalizeConfig(input)
// Each line should be trimmed of trailing whitespace only
lines := strings.Split(got, "\n")
for _, line := range lines {
if line == "" {
continue
}
assert.Equal(t, strings.TrimRight(line, " \t"), line, "line should have no trailing whitespace")
}
}
func TestNormalizeConfig_BlankLineCollapsing(t *testing.T) {
input := "/ip address\n\n\n\n/ip route\n"
got := NormalizeConfig(input)
assert.NotContains(t, got, "\n\n\n")
assert.Contains(t, got, "/ip address\n\n/ip route")
}
func TestNormalizeConfig_TrailingNewline(t *testing.T) {
tests := []struct {
name string
input string
}{
{"no trailing newline", "/ip address"},
{"one trailing newline", "/ip address\n"},
{"multiple trailing newlines", "/ip address\n\n\n"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NormalizeConfig(tt.input)
assert.True(t, strings.HasSuffix(got, "\n"), "should end with newline")
assert.False(t, strings.HasSuffix(got, "\n\n"), "should not end with double newline")
})
}
}
func TestNormalizeConfig_CommentPreservation(t *testing.T) {
input := "# 2024/01/15 10:30:00 by RouterOS 7.14\n# software id = ABC123\n# custom comment\n/ip address\n"
got := NormalizeConfig(input)
assert.Contains(t, got, "# software id = ABC123")
assert.Contains(t, got, "# custom comment")
}
func TestNormalizeConfig_FullPipeline(t *testing.T) {
input := "# 2024/01/15 10:30:00 by RouterOS 7.14\n" +
"# software id = ABC123\r\n" +
"/ip address \r\n" +
"add address=10.0.0.1/24 \r\n" +
"\r\n" +
"\r\n" +
"\r\n" +
"/ip route \r\n" +
"add dst-address=0.0.0.0/0 gateway=10.0.0.1\r\n"
expected := "# software id = ABC123\n" +
"/ip address\n" +
"add address=10.0.0.1/24\n" +
"\n" +
"/ip route\n" +
"add dst-address=0.0.0.0/0 gateway=10.0.0.1\n"
got := NormalizeConfig(input)
assert.Equal(t, expected, got)
}
func TestHashConfig(t *testing.T) {
normalized := "/ip address\nadd address=10.0.0.1/24\n"
hash := HashConfig(normalized)
assert.Len(t, hash, 64, "SHA256 hex should be 64 chars")
assert.Equal(t, strings.ToLower(hash), hash, "hash should be lowercase")
// Deterministic
assert.Equal(t, hash, HashConfig(normalized))
}
func TestNormalizeConfig_Idempotency(t *testing.T) {
input := "# 2024/01/15 10:30:00 by RouterOS 7.14\n" +
"# software id = ABC123\r\n" +
"/ip address \r\n" +
"\r\n\r\n\r\n" +
"/ip route\r\n"
first := NormalizeConfig(input)
second := NormalizeConfig(first)
assert.Equal(t, first, second, "NormalizeConfig should be idempotent")
}
func TestNormalizationVersion(t *testing.T) {
require.Equal(t, 1, NormalizationVersion, "NormalizationVersion should be 1")
}