kanboard-api/timezone_test.go

233 lines
6.3 KiB
Go
Raw Permalink Normal View History

package kanboard
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestClient_GetTimezone(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req JSONRPCRequest
json.NewDecoder(r.Body).Decode(&req)
if req.Method != "getTimezone" {
t.Errorf("expected method=getTimezone, got %s", req.Method)
}
resp := JSONRPCResponse{
JSONRPC: "2.0",
ID: req.ID,
Result: json.RawMessage(`"Europe/Berlin"`),
}
json.NewEncoder(w).Encode(resp)
}))
defer server.Close()
client := NewClient(server.URL).WithAPIToken("test-token")
tz, err := client.GetTimezone(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if tz != "Europe/Berlin" {
t.Errorf("expected Europe/Berlin, got %s", tz)
}
}
func TestClient_WithTimezone_ConvertsTaskTimestamps(t *testing.T) {
callCount := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req JSONRPCRequest
json.NewDecoder(r.Body).Decode(&req)
var result json.RawMessage
switch req.Method {
case "getTimezone":
callCount++
result = json.RawMessage(`"America/New_York"`)
case "getTask":
result = json.RawMessage(`{
"id": "1",
"title": "Test",
"description": "",
"date_creation": 1609459200,
"date_modification": 1609459200,
"date_completed": 0,
"date_started": 0,
"date_due": 0,
"date_moved": 0,
"color_id": "yellow",
"project_id": "1",
"column_id": "1",
"owner_id": "0",
"creator_id": "1",
"position": "1",
"is_active": "1",
"score": "0",
"category_id": "0",
"swimlane_id": "0",
"priority": "0",
"reference": "",
"recurrence_status": "0",
"recurrence_trigger": "0",
"recurrence_factor": "0",
"recurrence_timeframe": "0",
"recurrence_basedate": "0",
"recurrence_parent": "0",
"recurrence_child": "0"
}`)
default:
t.Errorf("unexpected method: %s", req.Method)
}
resp := JSONRPCResponse{
JSONRPC: "2.0",
ID: req.ID,
Result: result,
}
json.NewEncoder(w).Encode(resp)
}))
defer server.Close()
client := NewClient(server.URL).WithAPIToken("test-token").WithTimezone()
task, err := client.GetTask(context.Background(), 1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
loc, _ := time.LoadLocation("America/New_York")
expected := time.Unix(1609459200, 0).In(loc)
if !task.DateCreation.Time.Equal(expected) {
t.Errorf("expected time %v, got %v", expected, task.DateCreation.Time)
}
if task.DateCreation.Time.Location().String() != "America/New_York" {
t.Errorf("expected location America/New_York, got %s", task.DateCreation.Time.Location())
}
// Verify getTimezone was called exactly once
if callCount != 1 {
t.Errorf("expected getTimezone called once, got %d", callCount)
}
// Make a second call — should NOT call getTimezone again
_, err = client.GetTask(context.Background(), 1)
if err != nil {
t.Fatalf("unexpected error on second call: %v", err)
}
if callCount != 1 {
t.Errorf("expected getTimezone still called once, got %d", callCount)
}
}
func TestClient_WithTimezone_Disabled(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req JSONRPCRequest
json.NewDecoder(r.Body).Decode(&req)
if req.Method == "getTimezone" {
t.Error("getTimezone should not be called when timezone is disabled")
}
resp := JSONRPCResponse{
JSONRPC: "2.0",
ID: req.ID,
Result: json.RawMessage(`{
"id": "1",
"title": "Test",
"description": "",
"date_creation": 1609459200,
"date_modification": 0,
"date_completed": 0,
"date_started": 0,
"date_due": 0,
"date_moved": 0,
"color_id": "yellow",
"project_id": "1",
"column_id": "1",
"owner_id": "0",
"creator_id": "1",
"position": "1",
"is_active": "1",
"score": "0",
"category_id": "0",
"swimlane_id": "0",
"priority": "0",
"reference": "",
"recurrence_status": "0",
"recurrence_trigger": "0",
"recurrence_factor": "0",
"recurrence_timeframe": "0",
"recurrence_basedate": "0",
"recurrence_parent": "0",
"recurrence_child": "0"
}`),
}
json.NewEncoder(w).Encode(resp)
}))
defer server.Close()
client := NewClient(server.URL).WithAPIToken("test-token")
task, err := client.GetTask(context.Background(), 1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Without WithTimezone, timestamps stay as unmarshalled (Local from time.Unix)
if task.DateCreation.Time.Location() != time.Local {
t.Errorf("expected Local, got %s", task.DateCreation.Time.Location())
}
}
func TestConvertTimestamps(t *testing.T) {
loc, _ := time.LoadLocation("Asia/Tokyo")
client := &Client{timezone: loc}
t.Run("struct with Timestamp fields", func(t *testing.T) {
task := &Task{
DateCreation: Timestamp{Time: time.Unix(1609459200, 0)},
DateModification: Timestamp{Time: time.Unix(1609459200, 0)},
DateCompleted: Timestamp{}, // zero — should stay zero
}
client.convertTimestamps(task)
if task.DateCreation.Time.Location().String() != "Asia/Tokyo" {
t.Errorf("expected Asia/Tokyo, got %s", task.DateCreation.Time.Location())
}
if task.DateModification.Time.Location().String() != "Asia/Tokyo" {
t.Errorf("expected Asia/Tokyo, got %s", task.DateModification.Time.Location())
}
if !task.DateCompleted.IsZero() {
t.Error("zero timestamp should remain zero")
}
})
t.Run("slice of structs", func(t *testing.T) {
tasks := &[]Task{
{DateCreation: Timestamp{Time: time.Unix(1609459200, 0)}},
{DateCreation: Timestamp{Time: time.Unix(1609459200, 0)}},
}
client.convertTimestamps(tasks)
for i, task := range *tasks {
if task.DateCreation.Time.Location().String() != "Asia/Tokyo" {
t.Errorf("task[%d]: expected Asia/Tokyo, got %s", i, task.DateCreation.Time.Location())
}
}
})
t.Run("nil timezone is no-op", func(t *testing.T) {
noTzClient := &Client{}
task := &Task{DateCreation: Timestamp{Time: time.Unix(1609459200, 0)}}
noTzClient.convertTimestamps(task)
// Should not panic or change anything
if task.DateCreation.Time.Location() != time.Local {
t.Errorf("expected Local, got %s", task.DateCreation.Time.Location())
}
})
}