2026-01-15 18:25:12 +01:00
|
|
|
package kanboard
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestClient_GetAllCategories(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 != "getAllCategories" {
|
|
|
|
|
t.Errorf("expected method=getAllCategories, got %s", req.Method)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
params := req.Params.(map[string]any)
|
|
|
|
|
if params["project_id"].(float64) != 1 {
|
|
|
|
|
t.Errorf("expected project_id=1, got %v", params["project_id"])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp := JSONRPCResponse{
|
|
|
|
|
JSONRPC: "2.0",
|
|
|
|
|
ID: req.ID,
|
|
|
|
|
Result: json.RawMessage(`[
|
|
|
|
|
{"id": "1", "name": "Bug", "project_id": "1", "color_id": "red"},
|
|
|
|
|
{"id": "2", "name": "Feature", "project_id": "1", "color_id": "blue"}
|
|
|
|
|
]`),
|
|
|
|
|
}
|
|
|
|
|
json.NewEncoder(w).Encode(resp)
|
|
|
|
|
}))
|
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
|
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
|
|
|
|
|
|
|
|
|
categories, err := client.GetAllCategories(context.Background(), 1)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(categories) != 2 {
|
|
|
|
|
t.Errorf("expected 2 categories, got %d", len(categories))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if categories[0].Name != "Bug" {
|
|
|
|
|
t.Errorf("expected first category='Bug', got %s", categories[0].Name)
|
|
|
|
|
}
|
|
|
|
|
if categories[1].Name != "Feature" {
|
|
|
|
|
t.Errorf("expected second category='Feature', got %s", categories[1].Name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClient_GetAllCategories_Empty(t *testing.T) {
|
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
var req JSONRPCRequest
|
|
|
|
|
json.NewDecoder(r.Body).Decode(&req)
|
|
|
|
|
|
|
|
|
|
resp := JSONRPCResponse{
|
|
|
|
|
JSONRPC: "2.0",
|
|
|
|
|
ID: req.ID,
|
|
|
|
|
Result: json.RawMessage(`[]`),
|
|
|
|
|
}
|
|
|
|
|
json.NewEncoder(w).Encode(resp)
|
|
|
|
|
}))
|
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
|
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
|
|
|
|
|
|
|
|
|
categories, err := client.GetAllCategories(context.Background(), 1)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(categories) != 0 {
|
|
|
|
|
t.Errorf("expected 0 categories, got %d", len(categories))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClient_GetCategory(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 != "getCategory" {
|
|
|
|
|
t.Errorf("expected method=getCategory, got %s", req.Method)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
params := req.Params.(map[string]any)
|
|
|
|
|
if params["category_id"].(float64) != 5 {
|
|
|
|
|
t.Errorf("expected category_id=5, got %v", params["category_id"])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp := JSONRPCResponse{
|
|
|
|
|
JSONRPC: "2.0",
|
|
|
|
|
ID: req.ID,
|
|
|
|
|
Result: json.RawMessage(`{"id": "5", "name": "Bug", "project_id": "1", "color_id": "red"}`),
|
|
|
|
|
}
|
|
|
|
|
json.NewEncoder(w).Encode(resp)
|
|
|
|
|
}))
|
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
|
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
|
|
|
|
|
|
|
|
|
category, err := client.GetCategory(context.Background(), 5)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if int(category.ID) != 5 {
|
|
|
|
|
t.Errorf("expected ID=5, got %d", category.ID)
|
|
|
|
|
}
|
|
|
|
|
if category.Name != "Bug" {
|
|
|
|
|
t.Errorf("expected name='Bug', got %s", category.Name)
|
|
|
|
|
}
|
|
|
|
|
if category.ColorID != "red" {
|
|
|
|
|
t.Errorf("expected color_id='red', got %s", category.ColorID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClient_GetCategory_NotFound(t *testing.T) {
|
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
var req JSONRPCRequest
|
|
|
|
|
json.NewDecoder(r.Body).Decode(&req)
|
|
|
|
|
|
|
|
|
|
// Kanboard returns null for non-existent categories
|
|
|
|
|
resp := JSONRPCResponse{
|
|
|
|
|
JSONRPC: "2.0",
|
|
|
|
|
ID: req.ID,
|
|
|
|
|
Result: json.RawMessage(`null`),
|
|
|
|
|
}
|
|
|
|
|
json.NewEncoder(w).Encode(resp)
|
|
|
|
|
}))
|
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
|
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
|
|
|
|
|
|
|
|
|
_, err := client.GetCategory(context.Background(), 999)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected error for non-existent category")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !errors.Is(err, ErrCategoryNotFound) {
|
|
|
|
|
t.Errorf("expected ErrCategoryNotFound, got %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-29 09:20:13 +01:00
|
|
|
func TestClient_CreateCategory(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 != "createCategory" {
|
|
|
|
|
t.Errorf("expected method=createCategory, got %s", req.Method)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
params := req.Params.(map[string]any)
|
|
|
|
|
if params["project_id"].(float64) != 1 {
|
|
|
|
|
t.Errorf("expected project_id=1, got %v", params["project_id"])
|
|
|
|
|
}
|
|
|
|
|
if params["name"].(string) != "Bug" {
|
|
|
|
|
t.Errorf("expected name=Bug, got %v", params["name"])
|
|
|
|
|
}
|
|
|
|
|
if params["color_id"].(string) != "red" {
|
|
|
|
|
t.Errorf("expected color_id=red, got %v", params["color_id"])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp := JSONRPCResponse{
|
|
|
|
|
JSONRPC: "2.0",
|
|
|
|
|
ID: req.ID,
|
|
|
|
|
Result: json.RawMessage(`42`),
|
|
|
|
|
}
|
|
|
|
|
json.NewEncoder(w).Encode(resp)
|
|
|
|
|
}))
|
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
|
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
|
|
|
|
|
|
|
|
|
id, err := client.CreateCategory(context.Background(), 1, "Bug", "red")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if id != 42 {
|
|
|
|
|
t.Errorf("expected id=42, got %d", id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClient_CreateCategory_NoColor(t *testing.T) {
|
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
var req JSONRPCRequest
|
|
|
|
|
json.NewDecoder(r.Body).Decode(&req)
|
|
|
|
|
|
|
|
|
|
params := req.Params.(map[string]any)
|
|
|
|
|
if _, ok := params["color_id"]; ok {
|
|
|
|
|
t.Error("expected color_id to be absent")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp := JSONRPCResponse{
|
|
|
|
|
JSONRPC: "2.0",
|
|
|
|
|
ID: req.ID,
|
|
|
|
|
Result: json.RawMessage(`10`),
|
|
|
|
|
}
|
|
|
|
|
json.NewEncoder(w).Encode(resp)
|
|
|
|
|
}))
|
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
|
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
|
|
|
|
|
|
|
|
|
id, err := client.CreateCategory(context.Background(), 1, "Bug", "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if id != 10 {
|
|
|
|
|
t.Errorf("expected id=10, got %d", id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClient_CreateCategory_Failure(t *testing.T) {
|
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
var req JSONRPCRequest
|
|
|
|
|
json.NewDecoder(r.Body).Decode(&req)
|
|
|
|
|
|
|
|
|
|
resp := JSONRPCResponse{
|
|
|
|
|
JSONRPC: "2.0",
|
|
|
|
|
ID: req.ID,
|
|
|
|
|
Result: json.RawMessage(`false`),
|
|
|
|
|
}
|
|
|
|
|
json.NewEncoder(w).Encode(resp)
|
|
|
|
|
}))
|
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
|
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
|
|
|
|
|
|
|
|
|
_, err := client.CreateCategory(context.Background(), 1, "Bug", "")
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected error on failure")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClient_UpdateCategory(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 != "updateCategory" {
|
|
|
|
|
t.Errorf("expected method=updateCategory, got %s", req.Method)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
params := req.Params.(map[string]any)
|
|
|
|
|
if params["id"].(float64) != 5 {
|
|
|
|
|
t.Errorf("expected id=5, got %v", params["id"])
|
|
|
|
|
}
|
|
|
|
|
if params["name"].(string) != "Feature" {
|
|
|
|
|
t.Errorf("expected name=Feature, got %v", params["name"])
|
|
|
|
|
}
|
|
|
|
|
if params["color_id"].(string) != "blue" {
|
|
|
|
|
t.Errorf("expected color_id=blue, got %v", params["color_id"])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp := JSONRPCResponse{
|
|
|
|
|
JSONRPC: "2.0",
|
|
|
|
|
ID: req.ID,
|
|
|
|
|
Result: json.RawMessage(`true`),
|
|
|
|
|
}
|
|
|
|
|
json.NewEncoder(w).Encode(resp)
|
|
|
|
|
}))
|
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
|
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
|
|
|
|
|
|
|
|
|
err := client.UpdateCategory(context.Background(), 5, "Feature", "blue")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClient_UpdateCategory_Failure(t *testing.T) {
|
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
var req JSONRPCRequest
|
|
|
|
|
json.NewDecoder(r.Body).Decode(&req)
|
|
|
|
|
|
|
|
|
|
resp := JSONRPCResponse{
|
|
|
|
|
JSONRPC: "2.0",
|
|
|
|
|
ID: req.ID,
|
|
|
|
|
Result: json.RawMessage(`false`),
|
|
|
|
|
}
|
|
|
|
|
json.NewEncoder(w).Encode(resp)
|
|
|
|
|
}))
|
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
|
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
|
|
|
|
|
|
|
|
|
err := client.UpdateCategory(context.Background(), 5, "Feature", "")
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected error on failure")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClient_RemoveCategory(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 != "removeCategory" {
|
|
|
|
|
t.Errorf("expected method=removeCategory, got %s", req.Method)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
params := req.Params.(map[string]any)
|
|
|
|
|
if params["category_id"].(float64) != 3 {
|
|
|
|
|
t.Errorf("expected category_id=3, got %v", params["category_id"])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp := JSONRPCResponse{
|
|
|
|
|
JSONRPC: "2.0",
|
|
|
|
|
ID: req.ID,
|
|
|
|
|
Result: json.RawMessage(`true`),
|
|
|
|
|
}
|
|
|
|
|
json.NewEncoder(w).Encode(resp)
|
|
|
|
|
}))
|
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
|
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
|
|
|
|
|
|
|
|
|
err := client.RemoveCategory(context.Background(), 3)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClient_RemoveCategory_Failure(t *testing.T) {
|
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
var req JSONRPCRequest
|
|
|
|
|
json.NewDecoder(r.Body).Decode(&req)
|
|
|
|
|
|
|
|
|
|
resp := JSONRPCResponse{
|
|
|
|
|
JSONRPC: "2.0",
|
|
|
|
|
ID: req.ID,
|
|
|
|
|
Result: json.RawMessage(`false`),
|
|
|
|
|
}
|
|
|
|
|
json.NewEncoder(w).Encode(resp)
|
|
|
|
|
}))
|
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
|
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
|
|
|
|
|
|
|
|
|
err := client.RemoveCategory(context.Background(), 3)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected error on failure")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClient_GetCategoryByName(t *testing.T) {
|
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
var req JSONRPCRequest
|
|
|
|
|
json.NewDecoder(r.Body).Decode(&req)
|
|
|
|
|
|
|
|
|
|
resp := JSONRPCResponse{
|
|
|
|
|
JSONRPC: "2.0",
|
|
|
|
|
ID: req.ID,
|
|
|
|
|
Result: json.RawMessage(`[
|
|
|
|
|
{"id": "1", "name": "Bug", "project_id": "1", "color_id": "red"},
|
|
|
|
|
{"id": "2", "name": "Feature", "project_id": "1", "color_id": "blue"}
|
|
|
|
|
]`),
|
|
|
|
|
}
|
|
|
|
|
json.NewEncoder(w).Encode(resp)
|
|
|
|
|
}))
|
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
|
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
|
|
|
|
|
|
|
|
|
cat, err := client.GetCategoryByName(context.Background(), 1, "Feature")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if cat.Name != "Feature" {
|
|
|
|
|
t.Errorf("expected name=Feature, got %s", cat.Name)
|
|
|
|
|
}
|
|
|
|
|
if int(cat.ID) != 2 {
|
|
|
|
|
t.Errorf("expected id=2, got %d", cat.ID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClient_GetCategoryByName_NotFound(t *testing.T) {
|
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
var req JSONRPCRequest
|
|
|
|
|
json.NewDecoder(r.Body).Decode(&req)
|
|
|
|
|
|
|
|
|
|
resp := JSONRPCResponse{
|
|
|
|
|
JSONRPC: "2.0",
|
|
|
|
|
ID: req.ID,
|
|
|
|
|
Result: json.RawMessage(`[]`),
|
|
|
|
|
}
|
|
|
|
|
json.NewEncoder(w).Encode(resp)
|
|
|
|
|
}))
|
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
|
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
|
|
|
|
|
|
|
|
|
_, err := client.GetCategoryByName(context.Background(), 1, "Nonexistent")
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected error for non-existent category")
|
|
|
|
|
}
|
|
|
|
|
if !errors.Is(err, ErrCategoryNotFound) {
|
|
|
|
|
t.Errorf("expected ErrCategoryNotFound, got %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 18:25:12 +01:00
|
|
|
func TestClient_GetAllCategories_ContextCanceled(t *testing.T) {
|
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
select {}
|
|
|
|
|
}))
|
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
|
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
cancel()
|
|
|
|
|
|
|
|
|
|
_, err := client.GetAllCategories(ctx, 1)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected error due to canceled context")
|
|
|
|
|
}
|
|
|
|
|
}
|