kanboard-api/timestamp_test.go
Oliver Jakoubek dbac08ac1e Implement Timestamp type for Unix timestamp JSON handling
Add custom Timestamp type that wraps time.Time for Kanboard's
Unix timestamp format:

UnmarshalJSON supports:
- Unix timestamps as integers
- Empty strings and "0" strings as zero time
- Null values as zero time
- Numeric strings (e.g., "1609459200")

MarshalJSON returns:
- 0 for zero time
- Unix timestamp for non-zero time

Includes comprehensive tests for round-trip marshaling
and struct embedding scenarios.

Closes: kanboard-api-25y
2026-01-15 18:15:33 +01:00

203 lines
4.5 KiB
Go

package kanboard
import (
"encoding/json"
"testing"
"time"
)
func TestTimestamp_UnmarshalJSON_Integer(t *testing.T) {
tests := []struct {
name string
input string
expected time.Time
}{
{
name: "positive unix timestamp",
input: "1609459200",
expected: time.Unix(1609459200, 0), // 2021-01-01 00:00:00 UTC
},
{
name: "zero",
input: "0",
expected: time.Time{},
},
{
name: "recent timestamp",
input: "1704067200",
expected: time.Unix(1704067200, 0), // 2024-01-01 00:00:00 UTC
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var ts Timestamp
if err := json.Unmarshal([]byte(tt.input), &ts); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !ts.Time.Equal(tt.expected) {
t.Errorf("expected %v, got %v", tt.expected, ts.Time)
}
})
}
}
func TestTimestamp_UnmarshalJSON_String(t *testing.T) {
tests := []struct {
name string
input string
expected time.Time
}{
{
name: "empty string",
input: `""`,
expected: time.Time{},
},
{
name: "zero string",
input: `"0"`,
expected: time.Time{},
},
{
name: "numeric string",
input: `"1609459200"`,
expected: time.Unix(1609459200, 0),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var ts Timestamp
if err := json.Unmarshal([]byte(tt.input), &ts); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !ts.Time.Equal(tt.expected) {
t.Errorf("expected %v, got %v", tt.expected, ts.Time)
}
})
}
}
func TestTimestamp_UnmarshalJSON_Null(t *testing.T) {
var ts Timestamp
if err := json.Unmarshal([]byte("null"), &ts); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !ts.IsZero() {
t.Errorf("expected zero time for null, got %v", ts.Time)
}
}
func TestTimestamp_MarshalJSON(t *testing.T) {
tests := []struct {
name string
ts Timestamp
expected string
}{
{
name: "zero time",
ts: Timestamp{},
expected: "0",
},
{
name: "positive timestamp",
ts: Timestamp{Time: time.Unix(1609459200, 0)},
expected: "1609459200",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data, err := json.Marshal(tt.ts)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(data) != tt.expected {
t.Errorf("expected %s, got %s", tt.expected, string(data))
}
})
}
}
func TestTimestamp_RoundTrip(t *testing.T) {
// Test that marshal/unmarshal produces the same result
original := Timestamp{Time: time.Unix(1609459200, 0)}
data, err := json.Marshal(original)
if err != nil {
t.Fatalf("marshal error: %v", err)
}
var parsed Timestamp
if err := json.Unmarshal(data, &parsed); err != nil {
t.Fatalf("unmarshal error: %v", err)
}
if !original.Time.Equal(parsed.Time) {
t.Errorf("round trip failed: %v != %v", original.Time, parsed.Time)
}
}
func TestTimestamp_InStruct(t *testing.T) {
// Test Timestamp as part of a struct (simulating API response)
type Task struct {
ID int `json:"id"`
DateCreation Timestamp `json:"date_creation"`
DateDue Timestamp `json:"date_due"`
}
jsonData := `{"id":42,"date_creation":1609459200,"date_due":0}`
var task Task
if err := json.Unmarshal([]byte(jsonData), &task); err != nil {
t.Fatalf("unmarshal error: %v", err)
}
if task.ID != 42 {
t.Errorf("expected ID=42, got %d", task.ID)
}
if task.DateCreation.IsZero() {
t.Error("DateCreation should not be zero")
}
if !task.DateDue.IsZero() {
t.Error("DateDue should be zero")
}
}
func TestTimestamp_InStructWithStringTimestamp(t *testing.T) {
// Test with string timestamps (Kanboard sometimes returns these)
type Task struct {
ID int `json:"id"`
DateCreation Timestamp `json:"date_creation"`
DateDue Timestamp `json:"date_due"`
}
jsonData := `{"id":42,"date_creation":"1609459200","date_due":""}`
var task Task
if err := json.Unmarshal([]byte(jsonData), &task); err != nil {
t.Fatalf("unmarshal error: %v", err)
}
if task.ID != 42 {
t.Errorf("expected ID=42, got %d", task.ID)
}
if task.DateCreation.IsZero() {
t.Error("DateCreation should not be zero")
}
if !task.DateDue.IsZero() {
t.Error("DateDue should be zero")
}
}
func TestTimestamp_IsZero(t *testing.T) {
var zero Timestamp
if !zero.IsZero() {
t.Error("default Timestamp should be zero")
}
nonZero := Timestamp{Time: time.Unix(1609459200, 0)}
if nonZero.IsZero() {
t.Error("non-zero Timestamp should not be zero")
}
}