package sat import ( "os" "path/filepath" "strings" "testing" ) // Both shapes of the same file: three lines per satellite, and the two-line // form some feeds still serve. A reader that only understood one of them would // come back empty from a mirror and look like a network fault. func TestParseTLESetReadsBothShapes(t *testing.T) { three := issName + "\n" + issLine1 + "\n" + issLine2 + "\n" els, skipped, err := ParseTLESet(strings.NewReader(three)) if err != nil || len(els) != 1 || skipped != 0 { t.Fatalf("three-line: %d sats, %d skipped, err %v", len(els), skipped, err) } if els[0].Name != issName { t.Errorf("name %q", els[0].Name) } two := issLine1 + "\n" + issLine2 + "\n" els, _, err = ParseTLESet(strings.NewReader(two)) if err != nil || len(els) != 1 { t.Fatalf("two-line: %d sats, err %v", len(els), err) } if els[0].NORAD != 25544 { t.Errorf("a nameless entry lost its identity: %+v", els[0]) } } // One bad satellite must not cost the operator the other hundred and // ninety-nine — but the count of what was dropped has to come back, or a // silently shorter list reads as a complete one. func TestParseTLESetSkipsWhatItCannotRead(t *testing.T) { feed := strings.Join([]string{ "JUNK SATELLITE", "1 99999U 00000A 24298.00000000 .00000000 00000+0 00000+0 0 0000", // bad checksum "2 99999 00.0000 000.0000 0000000 000.0000 000.0000 00.00000000000000", "", issName, issLine1, issLine2, }, "\n") els, skipped, err := ParseTLESet(strings.NewReader(feed)) if err != nil { t.Fatalf("the whole feed was refused for one bad entry: %v", err) } if len(els) != 1 || els[0].Name != issName { t.Errorf("kept %d satellites: %+v", len(els), els) } if skipped != 1 { t.Errorf("skipped = %d, want 1 — a silently shorter list reads as a complete one", skipped) } // Nothing usable at all IS an error: an error page or a captive-portal login // parses as zero satellites, and that must never replace a good set. if _, _, err := ParseTLESet(strings.NewReader("login required")); err == nil { t.Error("an HTML error page was accepted as an element set") } } // The cache is what makes the first screen after a launch a full one — on a // train, or on a shack PC with no internet. func TestCacheRoundTrip(t *testing.T) { dir := t.TempDir() f := NewFetcher(dir) f.Logf = func(string, ...any) {} body := issName + "\n" + issLine1 + "\n" + issLine2 + "\n" if err := f.writeCache([]byte(body)); err != nil { t.Fatalf("write: %v", err) } if _, err := os.Stat(filepath.Join(dir, CacheName)); err != nil { t.Fatalf("the cache file is not where an operator would look for it: %v", err) } // And no leftovers: the temp file is renamed, not copied. if _, err := os.Stat(filepath.Join(dir, CacheName+".tmp")); err == nil { t.Error("the half-written file was left behind") } els, at, err := f.LoadCache() if err != nil || len(els) != 1 { t.Fatalf("load: %d sats, err %v", len(els), err) } if at.IsZero() { t.Error("the cache has no age, so nothing can say whether to trust it") } }