Ctrl K
ComunidadGuías…

pkg/models/postgres_v3_timestamp_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package models

import (
	"testing"
	"time"
)

// Regression for the postgres fuzzer failure: a 6-digit-year timestamp
// ("149206-12-15T16:39:16.394721Z") round-trips through the RFC3339Nano encoder
// but failed to decode because Go's "2006" layout token only accepts 4 digits.
func TestDecodeJSONTimestamp_WideAndNormalYears(t *testing.T) {
	cases := []struct {
		name string
		in   string
		want time.Time
	}{
		{
			name: "4-digit year (fast path)",
			in:   "2024-06-15T10:11:12.345678Z",
			want: time.Date(2024, time.June, 15, 10, 11, 12, 345678000, time.UTC),
		},
		{
			name: "6-digit year UTC (the fuzzer case)",
			in:   "149206-12-15T16:39:16.394721Z",
			want: time.Date(149206, time.December, 15, 16, 39, 16, 394721000, time.UTC),
		},
		{
			name: "5-digit year with offset preserved",
			in:   "12345-01-02T03:04:05.6789+05:30",
			want: time.Date(12345, time.January, 2, 3, 4, 5, 678900000, time.FixedZone("", 5*3600+30*60)),
		},
		{
			name: "postgres max year 294276",
			in:   "294276-12-31T23:59:59Z",
			want: time.Date(294276, time.December, 31, 23, 59, 59, 0, time.UTC),
		},
		{
			// Negative (signed) wide year — time.Time.Format emits a
			// leading '-', which the std layouts reject. Exercises the
			// sign-strip + re-apply path in parseWideYearRFC3339.
			name: "negative wide year UTC",
			in:   "-12345-06-15T10:11:12.5Z",
			want: time.Date(-12345, time.June, 15, 10, 11, 12, 500000000, time.UTC),
		},
		{
			name: "negative wide year with offset",
			in:   "-0753-04-21T00:00:00+01:00",
			want: time.Date(-753, time.April, 21, 0, 0, 0, 0, time.FixedZone("", 3600)),
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got, err := decodeJSONTimestamp(tc.in)
			if err != nil {
				t.Fatalf("decodeJSONTimestamp(%q) error: %v", tc.in, err)
			}
			if !got.Equal(tc.want) {
				t.Fatalf("decodeJSONTimestamp(%q) = %v, want %v", tc.in, got, tc.want)
			}
		})
	}
}

// Encoding a wide-year time.Time via the same RFC3339Nano layout and decoding it
// back must be lossless — the round-trip the postgres replay path relies on.
func TestDecodeJSONTimestamp_WideYearRoundTrip(t *testing.T) {
	for _, want := range []time.Time{
		time.Date(149206, time.December, 15, 16, 39, 16, 394721000, time.UTC),
		time.Date(99999, time.July, 4, 1, 2, 3, 0, time.UTC),
		// Negative wide year — guards the sign round-trip.
		time.Date(-12345, time.January, 2, 3, 4, 5, 678900000, time.UTC),
		// Non-UTC offset + fractional seconds — guards offset/precision
		// preservation through the wide-year fallback.
		time.Date(12345, time.January, 2, 3, 4, 5, 678900000, time.FixedZone("", 5*3600+30*60)),
	} {
		enc := want.Format(time.RFC3339Nano)
		got, err := decodeJSONTimestamp(enc)
		if err != nil {
			t.Fatalf("round-trip %q: %v", enc, err)
		}
		if !got.Equal(want) {
			t.Fatalf("round-trip %q = %v, want %v", enc, got, want)
		}
		// Equality only compares the instant; the JSON round-trip must be
		// lossless at the string level too, so re-encoding the decoded time
		// with RFC3339Nano must reproduce the exact input — preserving the
		// timezone offset and fractional-second precision, not just the
		// represented instant.
		if reEnc := got.Format(time.RFC3339Nano); reEnc != enc {
			t.Fatalf("round-trip %q re-encodes to %q (offset/precision not preserved)", enc, reEnc)
		}
	}
}

// Genuinely malformed input must still error (we don't want the wide-year
// fallback to mask real corruption).
func TestDecodeJSONTimestamp_Invalid(t *testing.T) {
	for _, in := range []string{"not-a-time", "2024-13-99T99:99:99Z", ""} {
		if _, err := decodeJSONTimestamp(in); err == nil {
			t.Fatalf("decodeJSONTimestamp(%q): expected error, got nil", in)
		}
	}
}