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
203 lines
4.5 KiB
Go
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")
|
|
}
|
|
}
|