package contracts import ( "strings" "testing" "time" ) const commandJSON = `{"schema_version":1,"device_id":"sim-001","request_id":"req-001","issued_at":"2026-10-06T10:00:00Z","expires_at":"2026-10-06T10:00:15Z","command":"ping"}` func TestDecodeAndDeadline(t *testing.T) { got, err := Decode("commands", []byte(commandJSON), "sim-001") if err != nil { t.Fatal(err) } cmd := got.(*Command) issued, _ := time.Parse(time.RFC3339, cmd.IssuedAt) for _, tc := range []struct { delta time.Duration valid bool }{{-time.Second, false}, {0, true}, {14 * time.Second, true}, {15 * time.Second, false}} { err = cmd.CheckDeadline(issued.Add(tc.delta)) if (err == nil) != tc.valid { t.Fatalf("deadline %s: %v", tc.delta, err) } } } func TestValidContracts(t *testing.T) { cases := map[string]string{ "telemetry": `{"schema_version":1,"device_id":"sim-001","message_id":"evt-1","boot_id":"boot-1","sequence":0,"timestamp":"2026-10-06T10:00:00Z","readings":{"uptime_s":12}}`, "status": `{"schema_version":1,"device_id":"sim-001","boot_id":"boot-1","state":"offline","will_configured_at":"2026-10-06T10:00:00Z"}`, "acks": `{"schema_version":1,"device_id":"sim-001","request_id":"req-1","timestamp":"2026-10-06T10:00:00Z","stage":"completed","ok":true,"result":{"pong":true}}`, } for kind, payload := range cases { t.Run(kind, func(t *testing.T) { if _, err := Decode(kind, []byte(payload), "sim-001"); err != nil { t.Fatal(err) } }) } } func TestRejectUnsafePayloads(t *testing.T) { for _, payload := range []string{ `null`, `[]`, commandJSON + `{}`, strings.Repeat("x", MaxPayloadBytes+1), strings.Replace(commandJSON, `"ping"`, `"vend"`, 1), strings.Replace(commandJSON, `"request_id":"req-001",`, "", 1), strings.Replace(commandJSON, `"schema_version":1`, `"schema_version":2`, 1), strings.Replace(commandJSON, `"command":"ping"`, `"command":"ping","password":"SECRET"`, 1), strings.Replace(commandJSON, `"command":"ping"`, `"command":"ping","command":"get_version"`, 1), strings.Replace(commandJSON, `"command":"ping"`, `"command":"ping","COMMAND":"get_version"`, 1), strings.Replace(commandJSON, `10:00:15Z`, `10:00:00Z`, 1), strings.Replace(commandJSON, `10:00:15Z`, `10:02:00Z`, 1), strings.Replace(commandJSON, `10:00:15Z`, `10:00:15+00:00`, 1), string([]byte{255, 0, 1}), } { if _, err := Decode("commands", []byte(payload), "sim-001"); err == nil { t.Fatal("invalid payload accepted") } else if strings.Contains(err.Error(), "SECRET") { t.Fatal("raw payload leaked") } } if _, err := Decode("commands", []byte(commandJSON), "other-device"); err == nil { t.Fatal("wrong device accepted") } } func TestNullReadingRejected(t *testing.T) { payload := `{"schema_version":1,"device_id":"sim-001","message_id":"evt-1","boot_id":"boot-1","sequence":0,"timestamp":"2026-10-06T10:00:00Z","readings":{"uptime_s":null}}` if _, err := Decode("telemetry", []byte(payload), "sim-001"); err == nil { t.Fatal("null must not become a numeric zero") } } func TestAckStages(t *testing.T) { base := `{"schema_version":1,"device_id":"sim-001","request_id":"req-1","timestamp":"2026-10-06T10:00:00Z",` for _, tc := range []struct { body string valid bool }{ {`"stage":"accepted","ok":true}`, true}, {`"stage":"accepted"}`, false}, {`"stage":"accepted","ok":false}`, false}, {`"stage":"sent","ok":true}`, false}, {`"stage":"completed","ok":true}`, false}, {`"stage":"completed","ok":true,"result":{"version":"v1.0.0"}}`, true}, {`"stage":"completed","ok":true,"result":{"pong":true,"version":"v1"}}`, false}, {`"stage":"failed","ok":false,"error":{"code":"expired","message":"too late"}}`, true}, {`"stage":"failed","ok":true,"error":{"code":"expired","message":"too late"}}`, false}, } { _, err := Decode("acks", []byte(base+tc.body), "sim-001") if (err == nil) != tc.valid { t.Fatalf("stage case %s: %v", tc.body, err) } } }