package datapoint import ( "archive/zip" "bytes" "encoding/json" "math" "reflect" "testing" ) func TestBatchRoundTrip(t *testing.T) { speed := 14.5 points := make([]DataPoint, 25) for i := range points { points[i] = DataPoint{Timestamp: uint64(1000 + i), Lat: 41.8, Long: -87.6, LatErr: 3, LongErr: 4, Speed: &speed} } raw, err := json.Marshal(points) if err != nil { t.Fatal(err) } zipped, err := ZipBatch(points) if err != nil { t.Fatal(err) } if len(zipped) >= len(raw) { t.Fatalf("batch did not compress: %d >= %d", len(zipped), len(raw)) } for name, payload := range map[string][]byte{"zip": zipped, "legacy": raw} { t.Run(name, func(t *testing.T) { got, err := UnzipBatch(payload) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(got, points) { t.Fatalf("round trip changed points: %+v", got) } }) } for _, points := range [][]DataPoint{nil, {}} { payload, err := ZipBatch(points) if err != nil { t.Fatal(err) } got, err := UnzipBatch(payload) if err != nil || !reflect.DeepEqual(got, points) { t.Fatalf("empty batch round trip: %v, %v", got, err) } } } func archive(t *testing.T, names []string, raw []byte) []byte { t.Helper() var b bytes.Buffer w := zip.NewWriter(&b) for _, name := range names { f, err := w.Create(name) if err != nil { t.Fatal(err) } if _, err = f.Write(raw); err != nil { t.Fatal(err) } } if err := w.Close(); err != nil { t.Fatal(err) } return b.Bytes() } func TestUnzipRejectsBadBatches(t *testing.T) { valid := archive(t, []string{"points.json"}, []byte("[]")) // Alter the stored CRC, leaving a structurally readable archive. corrupt := bytes.Clone(valid) central := bytes.Index(corrupt, []byte{'P', 'K', 1, 2}) if central < 0 { t.Fatal("missing ZIP directory") } corrupt[central+16] ^= 0xff cases := map[string][]byte{ "garbage": []byte("not a zip"), "truncated": valid[:len(valid)-10], "invalid json": archive(t, []string{"points.json"}, []byte("bad JSON")), "wrong entry": archive(t, []string{"../points.json"}, []byte("[]")), "multiple entries": archive(t, []string{"points.json", "other.json"}, []byte("[]")), "oversized compressed": make([]byte, MaxBatchBytes+1), "oversized expanded": archive(t, []string{"points.json"}, bytes.Repeat([]byte(" "), MaxBatchJSONBytes+1)), "checksum": corrupt, } for name, payload := range cases { t.Run(name, func(t *testing.T) { if _, err := UnzipBatch(payload); err == nil { t.Fatal("accepted invalid batch") } }) } } func TestZipRejectsInvalidPoints(t *testing.T) { if _, err := ZipBatch([]DataPoint{{Lat: math.NaN()}}); err == nil { t.Fatal("accepted non-JSON data") } }