blob: 44e83e50d1d45062131ee6b0f9c36c3f55784cee [file]
package watcher
import (
"os"
"path/filepath"
"testing"
"github.com/apache/trafficserver-ingress-controller/endpoint"
"github.com/apache/trafficserver-ingress-controller/proxy"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// newTestHandler creates a temporary AtsCacheHandler for testing.
// It overrides the handler's filePath to point to a temp cache.config file
// instead of the real /opt/ats/etc/trafficserver/cache.config.
func newTestHandler(t *testing.T) (*AtsCacheHandler, string) {
tmpDir := t.TempDir()
tmpFile := filepath.Join(tmpDir, "cache.config")
f, err := os.Create(tmpFile)
if err != nil {
t.Fatal(err)
}
defer f.Close()
// Ensure directory exists
//os.MkdirAll(filepath.Dir(tmpFile), 0755)
ep := createExampleEndpointWithFakeATSCache()
h := NewAtsCacheHandler("test-resource", &ep, tmpFile)
return h, tmpFile
}
// newCachingPolicy creates an unstructured ATSCachingPolicy object
// with the given name and rules. The rules must be []interface{} type.
func newCachingPolicy(name string, rules []interface{}) *unstructured.Unstructured {
u := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "example.com/v1alpha1",
"kind": "ATSCachingPolicy",
"metadata": map[string]interface{}{
"name": name,
"namespace": "default",
},
"spec": map[string]interface{}{
"rules": rules,
},
},
}
return u
}
// TestAddCachingPolicy verifies that calling h.Add(policy)
// writes the expected caching rule to cache.config and reloads configurations.
func TestAddCachingPolicy(t *testing.T) {
h, tmpFile := newTestHandler(t)
rules := []interface{}{
map[string]interface{}{
"primarySpecifier": map[string]interface{}{
"type": "url_regex",
"pattern": "/images/.*",
},
"action": "cache",
"ttl": "3600s",
},
}
policy := newCachingPolicy("policy1", rules)
h.Add(policy)
data, err := os.ReadFile(tmpFile)
if err != nil {
t.Fatalf("failed to read cache.config: %v", err)
}
content := string(data)
if content == "" || !containsLine(content, "url_regex=/images/.* ttl-in-cache=3600s") {
t.Errorf("expected cache.config to contain rule, got: %s", content)
}
}
// TestUpdateCachingPolicy verifies that calling h.Update(nil, newPolicy)
// modifies the existing caching rule in cache.config with new values and reloads configurations.
func TestUpdateCachingPolicy(t *testing.T) {
h, tmpFile := newTestHandler(t)
// Initial rule
initial := "url_regex=/images/.* ttl-in-cache=3600s\n"
if err := os.WriteFile(tmpFile, []byte(initial), 0644); err != nil {
t.Fatalf("failed to setup initial cache.config: %v", err)
}
// Update rule with new TTL
rules := []interface{}{
map[string]interface{}{
"primarySpecifier": map[string]interface{}{
"type": "url_regex",
"pattern": "/images/.*",
},
"action": "cache",
"ttl": "7200s",
},
}
newPolicy := newCachingPolicy("policy1", rules)
h.Update(nil, newPolicy)
data, err := os.ReadFile(tmpFile)
if err != nil {
t.Fatalf("failed to read cache.config: %v", err)
}
content := string(data)
if !containsLine(content, "url_regex=/images/.* ttl-in-cache=7200s") {
t.Errorf("expected updated TTL, got: %s", content)
}
}
// TestDeleteCachingPolicy verifies that calling h.Delete(policy)
// removes the matching caching rule from cache.config, but keeps unrelated lines intact and reloads configurations.
func TestDeleteCachingPolicy(t *testing.T) {
h, tmpFile := newTestHandler(t)
initial := "url_regex=/images/.* ttl-in-cache=3600s\nother_line=keepme\n"
if err := os.WriteFile(tmpFile, []byte(initial), 0644); err != nil {
t.Fatalf("failed to setup initial cache.config: %v", err)
}
rules := []interface{}{
map[string]interface{}{
"primarySpecifier": map[string]interface{}{
"type": "url_regex",
"pattern": "/images/.*",
},
"action": "cache",
"ttl": "3600s",
},
}
policy := newCachingPolicy("policy1", rules)
h.Delete(policy)
data, err := os.ReadFile(tmpFile)
if err != nil {
t.Fatalf("failed to read cache.config: %v", err)
}
content := string(data)
if containsLine(content, "url_regex=/images/.* ttl-in-cache=3600s") {
t.Errorf("expected rule to be deleted, got: %s", content)
}
if !containsLine(content, "other_line=keepme") {
t.Errorf("expected unrelated lines to remain, got: %s", content)
}
}
// containsLine checks if the given line exists in content.
func containsLine(content, line string) bool {
for _, l := range splitLines(content) {
if l == line {
return true
}
}
return false
}
// splitLines splits a string by newline into individual lines.
func splitLines(s string) []string {
var lines []string
current := ""
for _, r := range s {
if r == '\n' {
lines = append(lines, current)
current = ""
} else {
current += string(r)
}
}
if current != "" {
lines = append(lines, current)
}
return lines
}
// createExampleEndpointWithFakeATSCache creates a fake Endpoint with a FakeATSManager,
// used for unit testing without a real Traffic Server or Redis.
func createExampleEndpointWithFakeATSCache() endpoint.Endpoint {
ep := endpoint.Endpoint{
ATSManager: &proxy.FakeATSManager{
Namespace: "default",
IngressClass: "",
Config: make(map[string]string),
},
}
return ep
}