// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT package io import ( "encoding/binary" "os" "path/filepath" "testing" "sourcedock.dev/petrbalvin/tensor/internal/core" ) // The binary-table reader resolves header keywords through a map whose // documented rule is first-occurrence-wins, the behaviour the previous // linear scan answered. A hostile header may repeat a structural card // with a different value: this pin fixes that the reader follows the // first value of each repeated card. func TestLoadFITSTableRepeatedKeywordsFirstWins(t *testing.T) { body := make([]byte, 16) for r := range 2 { binary.BigEndian.PutUint64(body[r*8:], uint64(1000+r)) } cards := []string{ fitsStringCardRaw("XTENSION", "BINTABLE"), fitsIntCard("BITPIX", 8), fitsIntCard("NAXIS", 2), fitsIntCard("NAXIS1", 8), fitsIntCard("NAXIS2", 2), fitsIntCard("NAXIS2", 99), fitsIntCard("PCOUNT", 0), fitsIntCard("GCOUNT", 1), fitsIntCard("TFIELDS", 1), fitsIntCard("TFIELDS", 5), fitsStringCardRaw("TTYPE1", "COL1"), fitsStringCardRaw("TFORM1", "K"), fitsStringCardRaw("TFORM1", "D"), fitsEndCard(), } out := fitsAppendCards(nil, []string{ fitsBoolCard("SIMPLE", true), fitsIntCard("BITPIX", 8), fitsIntCard("NAXIS", 0), fitsBoolCard("EXTEND", true), fitsEndCard(), }) out = fitsAppendCards(out, cards) out = append(out, body...) out = fitsAppendZeroPad(out) path := filepath.Join(t.TempDir(), "repeated.fits") if err := os.WriteFile(path, out, 0o644); err != nil { t.Fatal(err) } table, err := LoadFITSTable(path) if err != nil { t.Fatalf("LoadFITSTable: %v", err) } if table.Rows != 2 { t.Fatalf("rows = %d, want 2 from the first NAXIS2", table.Rows) } if len(table.Columns) != 1 { t.Fatalf("columns = %d, want 1 from the first TFIELDS", len(table.Columns)) } col := table.Columns[0] if col == nil { t.Fatal("the first column came back nil; the repeated TFORM1 must still decode the first form") } if len(table.Text) > 0 && table.Text[0] != nil { t.Fatalf("column text = %v, want nil for a K column decoded from the first TFORM1", table.Text[0]) } if col.Dtype() != core.Int { t.Fatalf("column dtype = %s, want Int from the first TFORM1 (K)", col.Dtype()) } for i, want := range []int64{1000, 1001} { if got := col.RawInts()[i]; got != want { t.Fatalf("value %d = %d, want %d", i, got, want) } } }