Add cache mechanism to KorAP client

Change-Id: Ie3f3d48611f039904f22a19cf6299a3c43fe8bbe
diff --git a/service/client_test.go b/service/client_test.go
index 0009d71..6ebb61e 100644
--- a/service/client_test.go
+++ b/service/client_test.go
@@ -318,3 +318,154 @@
 
 	assert.Equal(t, "https://example.com/", client.GetBaseURL())
 }
+
+// TestClientCaching tests client caching functionality
+func TestClientCaching(t *testing.T) {
+	requestCount := 0
+
+	// Create a mock server that counts requests
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		requestCount++
+		// Use URL.Path to get clean path without query parameters
+		path := r.URL.Path
+		switch path {
+		case "/cached":
+			w.Header().Set("Content-Type", "application/json")
+			json.NewEncoder(w).Encode(map[string]interface{}{
+				"data":          "cached response",
+				"request_count": requestCount,
+			})
+		case "/search":
+			w.Header().Set("Content-Type", "application/json")
+			query := r.URL.Query().Get("q")
+			json.NewEncoder(w).Encode(map[string]interface{}{
+				"query":         query,
+				"results":       []string{"result1", "result2"},
+				"request_count": requestCount,
+			})
+		default:
+			w.WriteHeader(http.StatusNotFound)
+			json.NewEncoder(w).Encode(map[string]string{
+				"error": fmt.Sprintf("Not found: %s", path),
+			})
+		}
+	}))
+	defer server.Close()
+
+	t.Run("cache enabled", func(t *testing.T) {
+		cacheConfig := DefaultCacheConfig()
+		cacheConfig.DefaultTTL = 1 * time.Minute
+
+		client, err := NewClient(ClientOptions{
+			BaseURL:     server.URL,
+			CacheConfig: &cacheConfig,
+		})
+		assert.NoError(t, err)
+		defer client.Close()
+
+		ctx := context.Background()
+		requestCount = 0
+
+		// First request - should hit the server
+		var result1 map[string]interface{}
+		err = client.GetJSON(ctx, "/cached", &result1)
+		assert.NoError(t, err)
+		assert.Equal(t, float64(1), result1["request_count"])
+
+		// Second request - should hit the cache
+		var result2 map[string]interface{}
+		err = client.GetJSON(ctx, "/cached", &result2)
+		assert.NoError(t, err)
+		assert.Equal(t, float64(1), result2["request_count"]) // Same as first request
+
+		// Verify cache statistics
+		cache := client.GetCache()
+		assert.NotNil(t, cache)
+		stats := cache.Stats()
+		assert.True(t, stats["enabled"].(bool))
+		assert.Equal(t, 1, stats["size"].(int))
+		assert.True(t, stats["hits"].(int64) > 0)
+	})
+
+	t.Run("cache disabled", func(t *testing.T) {
+		cacheConfig := CacheConfig{Enabled: false}
+
+		client, err := NewClient(ClientOptions{
+			BaseURL:     server.URL,
+			CacheConfig: &cacheConfig,
+		})
+		assert.NoError(t, err)
+		defer client.Close()
+
+		ctx := context.Background()
+		requestCount = 0
+
+		// Both requests should hit the server
+		var result1 map[string]interface{}
+		err = client.GetJSON(ctx, "/cached", &result1)
+		assert.NoError(t, err)
+		assert.Equal(t, float64(1), result1["request_count"])
+
+		var result2 map[string]interface{}
+		err = client.GetJSON(ctx, "/cached", &result2)
+		assert.NoError(t, err)
+		assert.Equal(t, float64(2), result2["request_count"]) // Different from first request
+
+		// Verify cache is disabled
+		cache := client.GetCache()
+		assert.NotNil(t, cache)
+		stats := cache.Stats()
+		assert.False(t, stats["enabled"].(bool))
+	})
+
+	t.Run("cache expiry", func(t *testing.T) {
+		cacheConfig := CacheConfig{
+			Enabled:     true,
+			DefaultTTL:  50 * time.Millisecond,
+			SearchTTL:   50 * time.Millisecond,
+			MetadataTTL: 50 * time.Millisecond,
+			MaxSize:     100,
+		}
+
+		client, err := NewClient(ClientOptions{
+			BaseURL:     server.URL,
+			CacheConfig: &cacheConfig,
+		})
+		assert.NoError(t, err)
+		defer client.Close()
+
+		ctx := context.Background()
+		requestCount = 0
+
+		// First request
+		var result1 map[string]interface{}
+		err = client.GetJSON(ctx, "/cached", &result1)
+		assert.NoError(t, err)
+		assert.Equal(t, float64(1), result1["request_count"])
+
+		// Wait for cache to expire
+		time.Sleep(100 * time.Millisecond)
+
+		// Second request should hit server again
+		var result2 map[string]interface{}
+		err = client.GetJSON(ctx, "/cached", &result2)
+		assert.NoError(t, err)
+		assert.Equal(t, float64(2), result2["request_count"])
+	})
+}
+
+// TestClientDefaultCache tests that clients get default cache when no config is provided
+func TestClientDefaultCache(t *testing.T) {
+	client, err := NewClient(ClientOptions{
+		BaseURL: "https://example.com",
+	})
+	assert.NoError(t, err)
+	defer client.Close()
+
+	cache := client.GetCache()
+	assert.NotNil(t, cache)
+
+	stats := cache.Stats()
+	assert.True(t, stats["enabled"].(bool))
+	assert.Equal(t, 1000, stats["max_size"].(int))
+}