logging_path is now manual, automatic_flyapp or automatic_flyagentapp. The Fly App's entries are backed by the aircraft's flight recorder and keep the missing-FDR red flag; the Fly Agent App's come from Remote ID, which is not a flight recorder, so they must carry the § 5 fields like a manual entry. The API maps the old "automatic" onto automatic_flyapp so Fly App builds already on devices keep filing. The migration has been applied to the remote PocketBase. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90 lines
3.2 KiB
Go
90 lines
3.2 KiB
Go
package api
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func completeFlight(path string) flightRecord {
|
|
return flightRecord{
|
|
OperationDate: "2026-10-22", StartTime: "14:05", EndTime: "14:17", Drone: "d1",
|
|
AreaRoute: "Field north of the hangar", MaxAltitudeAGL: 80, PilotName: "pilot@example.com",
|
|
Category: "open", Purpose: "commercial", LoggingPath: path,
|
|
}
|
|
}
|
|
|
|
func TestNormalizeLoggingPath(t *testing.T) {
|
|
cases := map[string]string{
|
|
"automatic": loggingFlyApp,
|
|
" automatic ": loggingFlyApp,
|
|
"automatic_flyapp": loggingFlyApp,
|
|
"automatic_flyagentapp": loggingFlyAgentApp,
|
|
"manual": loggingManual,
|
|
"": "",
|
|
}
|
|
for in, want := range cases {
|
|
if got := normalizeLoggingPath(in); got != want {
|
|
t.Errorf("normalizeLoggingPath(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEffectiveLoggingPathDefaults(t *testing.T) {
|
|
autolog := &droneRecord{AutologsFlights: true}
|
|
if got := effectiveLoggingPath(flightRecord{}, autolog); got != loggingFlyApp {
|
|
t.Errorf("autologging drone, blank path = %q, want %q", got, loggingFlyApp)
|
|
}
|
|
if got := effectiveLoggingPath(flightRecord{}, &droneRecord{}); got != loggingManual {
|
|
t.Errorf("plain drone, blank path = %q, want %q", got, loggingManual)
|
|
}
|
|
if got := effectiveLoggingPath(flightRecord{LoggingPath: "automatic"}, nil); got != loggingFlyApp {
|
|
t.Errorf("legacy automatic = %q, want %q", got, loggingFlyApp)
|
|
}
|
|
}
|
|
|
|
func TestValidateFlightByPath(t *testing.T) {
|
|
// The Fly App path is backed by the FDR, so a sparse entry is accepted.
|
|
sparse := flightRecord{OperationDate: "2026-10-22", Drone: "d1", LoggingPath: loggingFlyApp}
|
|
if msg, ok := validateFlight(sparse, nil); !ok {
|
|
t.Errorf("sparse automatic_flyapp entry rejected: %s", msg)
|
|
}
|
|
// Manual and Fly Agent entries have no FDR behind them: § 5 fields are required.
|
|
for _, path := range []string{loggingManual, loggingFlyAgentApp} {
|
|
sparse.LoggingPath = path
|
|
msg, ok := validateFlight(sparse, nil)
|
|
if ok || !strings.Contains(msg, path) {
|
|
t.Errorf("sparse %s entry: ok=%v msg=%q, want a rejection naming the path", path, ok, msg)
|
|
}
|
|
if msg, ok := validateFlight(completeFlight(path), nil); !ok {
|
|
t.Errorf("complete %s entry rejected: %s", path, msg)
|
|
}
|
|
}
|
|
if _, ok := validateFlight(completeFlight("companion"), nil); ok {
|
|
t.Error("unknown logging path accepted")
|
|
}
|
|
}
|
|
|
|
func TestComplianceRedFlagsByPath(t *testing.T) {
|
|
autolog := &droneRecord{AutologsFlights: true}
|
|
hasFDRFlag := func(c compliance) bool {
|
|
for _, f := range c.RedFlags {
|
|
if strings.Contains(f, "no FDR log") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
if c := computeCompliance(completeFlight(loggingFlyApp), autolog); !hasFDRFlag(c) {
|
|
t.Error("automatic_flyapp without an FDR url should be flagged")
|
|
}
|
|
if c := computeCompliance(completeFlight(loggingFlyAgentApp), autolog); hasFDRFlag(c) {
|
|
t.Error("automatic_flyagentapp never has an FDR; it must not be flagged for one")
|
|
}
|
|
agent := completeFlight(loggingFlyAgentApp)
|
|
agent.AreaRoute = ""
|
|
c := computeCompliance(agent, nil)
|
|
if c.LoggingPath != loggingFlyAgentApp || len(c.RedFlags) == 0 {
|
|
t.Errorf("automatic_flyagentapp missing area: path=%q flags=%v, want a missing-field flag", c.LoggingPath, c.RedFlags)
|
|
}
|
|
}
|