|
1 | 1 | package dashboard |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "os" |
4 | 5 | "strings" |
5 | 6 | "testing" |
6 | 7 | ) |
@@ -78,3 +79,79 @@ func TestSanitizeDashboardNextNormalizesAndConstrainsPath(t *testing.T) { |
78 | 79 | }) |
79 | 80 | } |
80 | 81 | } |
| 82 | + |
| 83 | +// withEngramTimezone forces ENGRAM_TIMEZONE for the test body and restores |
| 84 | +// the original value afterwards, so timezone-sensitive tests stay isolated. |
| 85 | +func withEngramTimezone(t *testing.T, tz string) { |
| 86 | + t.Helper() |
| 87 | + old, hadOld := os.LookupEnv("ENGRAM_TIMEZONE") |
| 88 | + t.Setenv("ENGRAM_TIMEZONE", tz) |
| 89 | + t.Cleanup(func() { |
| 90 | + if hadOld { |
| 91 | + os.Setenv("ENGRAM_TIMEZONE", old) |
| 92 | + return |
| 93 | + } |
| 94 | + os.Unsetenv("ENGRAM_TIMEZONE") |
| 95 | + }) |
| 96 | +} |
| 97 | + |
| 98 | +// TestFormatTimestampParsesSQLiteStyleInput is the regression test for the bug |
| 99 | +// reported on #169 by @quirozino. The legacy dashboard helper called |
| 100 | +// time.Parse(time.RFC3339Nano, ts) which silently failed on SQLite-style |
| 101 | +// "YYYY-MM-DD HH:MM:SS" values and leaked raw UTC strings into the UI. The new |
| 102 | +// helper delegates to timeutil and must convert these values correctly. |
| 103 | +func TestFormatTimestampParsesSQLiteStyleInput(t *testing.T) { |
| 104 | + withEngramTimezone(t, "America/Bogota") // UTC-5 |
| 105 | + |
| 106 | + // SQLite-style timestamp stored as UTC noon → 07:00 in Bogota. |
| 107 | + got := formatTimestamp("2026-05-22 12:00:00") |
| 108 | + want := "22 May 2026 07:00" |
| 109 | + if got != want { |
| 110 | + t.Fatalf("formatTimestamp(SQLite-style) = %q, want %q (regression of #169 dashboard bug)", got, want) |
| 111 | + } |
| 112 | +} |
| 113 | + |
| 114 | +func TestFormatTimestampParsesRFC3339Input(t *testing.T) { |
| 115 | + withEngramTimezone(t, "America/Bogota") |
| 116 | + |
| 117 | + got := formatTimestamp("2026-05-22T12:00:00Z") |
| 118 | + want := "22 May 2026 07:00" |
| 119 | + if got != want { |
| 120 | + t.Fatalf("formatTimestamp(RFC3339) = %q, want %q", got, want) |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +func TestFormatTimestampHonorsEngramTimezone(t *testing.T) { |
| 125 | + withEngramTimezone(t, "Europe/Madrid") // UTC+2 in May (CEST) |
| 126 | + |
| 127 | + got := formatTimestamp("2026-05-22T09:56:00Z") |
| 128 | + want := "22 May 2026 11:56" |
| 129 | + if got != want { |
| 130 | + t.Fatalf("formatTimestamp under Europe/Madrid = %q, want %q", got, want) |
| 131 | + } |
| 132 | +} |
| 133 | + |
| 134 | +func TestFormatTimestampEmptyReturnsDash(t *testing.T) { |
| 135 | + if got := formatTimestamp(""); got != "-" { |
| 136 | + t.Fatalf("formatTimestamp(empty) = %q, want %q", got, "-") |
| 137 | + } |
| 138 | + if got := formatTimestamp(" "); got != "-" { |
| 139 | + t.Fatalf("formatTimestamp(whitespace) = %q, want %q", got, "-") |
| 140 | + } |
| 141 | +} |
| 142 | + |
| 143 | +func TestFormatTimestampUnparseableReturnsRaw(t *testing.T) { |
| 144 | + // Malformed input must not be lost; the helper returns it as-is so the UI |
| 145 | + // stays informative even when the source data is unexpected. |
| 146 | + in := "not-a-timestamp" |
| 147 | + if got := formatTimestamp(in); got != in { |
| 148 | + t.Fatalf("formatTimestamp(unparseable) = %q, want %q", got, in) |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +func TestFormatTimestampStrEmptyReturnsNever(t *testing.T) { |
| 153 | + if got := formatTimestampStr(""); got != "Never" { |
| 154 | + t.Fatalf("formatTimestampStr(empty) = %q, want %q", got, "Never") |
| 155 | + } |
| 156 | +} |
| 157 | + |
0 commit comments