From a64e163af528cc930235df4b340307290179e0e0 Mon Sep 17 00:00:00 2001 From: Anju Pathak Date: Thu, 9 Oct 2025 00:09:54 +0000 Subject: [PATCH 1/4] feat: add risk-profile app Signed-off-by: Anju Pathak --- risk-profile/curl.sh | 12 +++ risk-profile/go.mod | 3 + risk-profile/main.go | 172 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 risk-profile/curl.sh create mode 100644 risk-profile/go.mod create mode 100644 risk-profile/main.go diff --git a/risk-profile/curl.sh b/risk-profile/curl.sh new file mode 100644 index 00000000..e1c2c340 --- /dev/null +++ b/risk-profile/curl.sh @@ -0,0 +1,12 @@ +# Body tests +curl http://localhost:8080/users-low-risk +curl http://localhost:8080/users-medium-risk +curl http://localhost:8080/users-medium-risk-with-addition +curl http://localhost:8080/users-high-risk-type +curl http://localhost:8080/users-high-risk-removal + +# Status and Header tests +curl http://localhost:8080/status-change-high-risk +curl http://localhost:8080/content-type-change-high-risk +curl http://localhost:8080/header-change-medium-risk +curl http://localhost:8080/noisy-header diff --git a/risk-profile/go.mod b/risk-profile/go.mod new file mode 100644 index 00000000..d46a0ffd --- /dev/null +++ b/risk-profile/go.mod @@ -0,0 +1,3 @@ +module github.com/keploy/samples-go/risk-profile + +go 1.24.2 diff --git a/risk-profile/main.go b/risk-profile/main.go new file mode 100644 index 00000000..6b3ca5e6 --- /dev/null +++ b/risk-profile/main.go @@ -0,0 +1,172 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "time" +) + +// --- V1 Data Structures and Data --- +type UserV1 struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +// The base data remains a slice, but we will only serve the first element. +var originalUsers = []UserV1{ + {ID: 1, Name: "Alice", Email: "alice@example.com"}, + {ID: 2, Name: "Bob", Email: "bob@example.com"}, +} + +// --- V2 Data Structures for High-Risk Scenarios --- +type UserHighRiskTypeChange struct { + ID string `json:"id"` // Type changed from int to string + Name string `json:"name"` + Email string `json:"email"` +} + +// --- API Handlers --- + +// BODY: LOW RISK (Only new fields added) +func getUsersLowRisk(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if os.Getenv("KEPLOY_MODE") == "test" { + dataWithAddedField := struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + IsActive bool `json:"isActive"` // New field + }{ + ID: 1, Name: "Alice", Email: "alice@example.com", IsActive: true, + } + json.NewEncoder(w).Encode(dataWithAddedField) + return + } + json.NewEncoder(w).Encode(originalUsers[0]) // Return a single object +} + +// BODY: MEDIUM RISK (Value changes only) +func getUsersMediumRisk(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if os.Getenv("KEPLOY_MODE") == "test" { + dataWithValueChange := UserV1{ + ID: 1, Name: "Alicia", Email: "alice@example.com", // Name changed + } + json.NewEncoder(w).Encode(dataWithValueChange) + return + } + json.NewEncoder(w).Encode(originalUsers[0]) // Return a single object +} + +// BODY: MEDIUM RISK (New fields added + value changes) +func getUsersMediumRiskWithAddition(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if os.Getenv("KEPLOY_MODE") == "test" { + dataWithAdditionAndChange := struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + IsActive bool `json:"isActive"` // New field + }{ + ID: 1, Name: "Alicia", Email: "alice@example.com", IsActive: true, // Name changed AND IsActive added + } + json.NewEncoder(w).Encode(dataWithAdditionAndChange) + return + } + json.NewEncoder(w).Encode(originalUsers[0]) // Return a single object +} + +// BODY: HIGH RISK (Field's type changes) +func getUsersHighRiskType(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if os.Getenv("KEPLOY_MODE") == "test" { + dataWithTypeChange := UserHighRiskTypeChange{ + ID: "user-001", Name: "Alice", Email: "alice@example.com", + } + json.NewEncoder(w).Encode(dataWithTypeChange) + return + } + json.NewEncoder(w).Encode(originalUsers[0]) // Return a single object +} + +// BODY: HIGH RISK (Field is removed) +func getUsersHighRiskRemoval(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if os.Getenv("KEPLOY_MODE") == "test" { + dataWithFieldRemoved := struct { + ID int `json:"id"` + Name string `json:"name"` + }{ + ID: 1, Name: "Alice", + } + json.NewEncoder(w).Encode(dataWithFieldRemoved) + return + } + json.NewEncoder(w).Encode(originalUsers[0]) // Return a single object +} + +// STATUS: HIGH RISK (Status code changes from 200 to 400) +func statusChangeHighRisk(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + println("KEPLOY_MODE:", os.Getenv("KEPLOY_MODE")) + if os.Getenv("KEPLOY_MODE") == "test" { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(`{"error": "Bad Request"}`)) + return + } + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status": "OK"}`)) +} + +// HEADER: HIGH RISK (Content-Type changes) +func contentTypeChangeHighRisk(w http.ResponseWriter, r *http.Request) { + if os.Getenv("KEPLOY_MODE") == "test" { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + w.Write([]byte("This is now plain text.")) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"message": "This is JSON."}`)) +} + +// HEADER: MEDIUM RISK (A non-critical header changes) +func headerChangeMediumRisk(w http.ResponseWriter, r *http.Request) { + if os.Getenv("KEPLOY_MODE") == "test" { + w.Header().Set("X-Custom-Header", "new-value-987") + } else { + w.Header().Set("X-Custom-Header", "initial-value-123") + } + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status": "header test"}`)) +} + +// NOISY: This should PASS if noise is configured correctly. +func noisyHeader(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Date", time.Now().UTC().Format(http.TimeFormat)) + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"message": "Check the Date header!"}`)) +} + +func main() { + log.Println("Application starting...") + http.HandleFunc("/users-low-risk", getUsersLowRisk) + http.HandleFunc("/users-medium-risk", getUsersMediumRisk) + http.HandleFunc("/users-medium-risk-with-addition", getUsersMediumRiskWithAddition) + http.HandleFunc("/users-high-risk-type", getUsersHighRiskType) + http.HandleFunc("/users-high-risk-removal", getUsersHighRiskRemoval) + http.HandleFunc("/status-change-high-risk", statusChangeHighRisk) + http.HandleFunc("/content-type-change-high-risk", contentTypeChangeHighRisk) + http.HandleFunc("/header-change-medium-risk", headerChangeMediumRisk) + http.HandleFunc("/noisy-header", noisyHeader) + port := "8080" + log.Printf("Server starting on port %s...", port) + if err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil); err != nil { + log.Fatalf("could not start server: %s\n", err) + } +} From 2cde549c0cbba6e28ec08b5d690fe82eb37884fc Mon Sep 17 00:00:00 2001 From: Anju Pathak Date: Sun, 12 Oct 2025 18:42:47 +0000 Subject: [PATCH 2/4] fix: use separate branches for application changes Signed-off-by: Anju Pathak --- risk-profile/curl.sh | 6 +- risk-profile/main.go | 174 ++++++++++++++++++++----------------------- 2 files changed, 86 insertions(+), 94 deletions(-) diff --git a/risk-profile/curl.sh b/risk-profile/curl.sh index e1c2c340..b7df611e 100644 --- a/risk-profile/curl.sh +++ b/risk-profile/curl.sh @@ -9,4 +9,8 @@ curl http://localhost:8080/users-high-risk-removal curl http://localhost:8080/status-change-high-risk curl http://localhost:8080/content-type-change-high-risk curl http://localhost:8080/header-change-medium-risk -curl http://localhost:8080/noisy-header + +# Combined tests +curl http://localhost:8080/status-body-change +curl http://localhost:8080/header-body-change +curl http://localhost:8080/status-body-header-change \ No newline at end of file diff --git a/risk-profile/main.go b/risk-profile/main.go index 6b3ca5e6..c13f193d 100644 --- a/risk-profile/main.go +++ b/risk-profile/main.go @@ -5,152 +5,138 @@ import ( "fmt" "log" "net/http" - "os" "time" ) -// --- V1 Data Structures and Data --- type UserV1 struct { ID int `json:"id"` Name string `json:"name"` Email string `json:"email"` } -// The base data remains a slice, but we will only serve the first element. var originalUsers = []UserV1{ {ID: 1, Name: "Alice", Email: "alice@example.com"}, - {ID: 2, Name: "Bob", Email: "bob@example.com"}, } -// --- V2 Data Structures for High-Risk Scenarios --- -type UserHighRiskTypeChange struct { - ID string `json:"id"` // Type changed from int to string - Name string `json:"name"` - Email string `json:"email"` -} - -// --- API Handlers --- - -// BODY: LOW RISK (Only new fields added) func getUsersLowRisk(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - if os.Getenv("KEPLOY_MODE") == "test" { - dataWithAddedField := struct { - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - IsActive bool `json:"isActive"` // New field - }{ - ID: 1, Name: "Alice", Email: "alice@example.com", IsActive: true, - } - json.NewEncoder(w).Encode(dataWithAddedField) - return + user := originalUsers[0] + response := map[string]interface{}{ + "id": user.ID, + "name": user.Name, + "email": user.Email, + "timestamp": time.Now().Unix(), } - json.NewEncoder(w).Encode(originalUsers[0]) // Return a single object + json.NewEncoder(w).Encode(response) } -// BODY: MEDIUM RISK (Value changes only) func getUsersMediumRisk(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - if os.Getenv("KEPLOY_MODE") == "test" { - dataWithValueChange := UserV1{ - ID: 1, Name: "Alicia", Email: "alice@example.com", // Name changed - } - json.NewEncoder(w).Encode(dataWithValueChange) - return + user := originalUsers[0] + response := map[string]interface{}{ + "id": user.ID, + "name": user.Name, + "email": user.Email, + "timestamp": time.Now().Unix(), } - json.NewEncoder(w).Encode(originalUsers[0]) // Return a single object + json.NewEncoder(w).Encode(response) } -// BODY: MEDIUM RISK (New fields added + value changes) func getUsersMediumRiskWithAddition(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - if os.Getenv("KEPLOY_MODE") == "test" { - dataWithAdditionAndChange := struct { - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - IsActive bool `json:"isActive"` // New field - }{ - ID: 1, Name: "Alicia", Email: "alice@example.com", IsActive: true, // Name changed AND IsActive added - } - json.NewEncoder(w).Encode(dataWithAdditionAndChange) - return + user := originalUsers[0] + response := map[string]interface{}{ + "id": user.ID, + "name": user.Name, + "email": user.Email, + "timestamp": time.Now().Unix(), } - json.NewEncoder(w).Encode(originalUsers[0]) // Return a single object + json.NewEncoder(w).Encode(response) } -// BODY: HIGH RISK (Field's type changes) func getUsersHighRiskType(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - if os.Getenv("KEPLOY_MODE") == "test" { - dataWithTypeChange := UserHighRiskTypeChange{ - ID: "user-001", Name: "Alice", Email: "alice@example.com", - } - json.NewEncoder(w).Encode(dataWithTypeChange) - return + user := originalUsers[0] + response := map[string]interface{}{ + "id": user.ID, + "name": user.Name, + "email": user.Email, + "timestamp": time.Now().Unix(), } - json.NewEncoder(w).Encode(originalUsers[0]) // Return a single object + json.NewEncoder(w).Encode(response) } -// BODY: HIGH RISK (Field is removed) func getUsersHighRiskRemoval(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - if os.Getenv("KEPLOY_MODE") == "test" { - dataWithFieldRemoved := struct { - ID int `json:"id"` - Name string `json:"name"` - }{ - ID: 1, Name: "Alice", - } - json.NewEncoder(w).Encode(dataWithFieldRemoved) - return + user := originalUsers[0] + response := map[string]interface{}{ + "id": user.ID, + "name": user.Name, + "email": user.Email, + "timestamp": time.Now().Unix(), } - json.NewEncoder(w).Encode(originalUsers[0]) // Return a single object + json.NewEncoder(w).Encode(response) } -// STATUS: HIGH RISK (Status code changes from 200 to 400) func statusChangeHighRisk(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - println("KEPLOY_MODE:", os.Getenv("KEPLOY_MODE")) - if os.Getenv("KEPLOY_MODE") == "test" { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(`{"error": "Bad Request"}`)) - return - } w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"status": "OK"}`)) + response := map[string]interface{}{ + "status": "OK", + "timestamp": time.Now().Unix(), + } + json.NewEncoder(w).Encode(response) } -// HEADER: HIGH RISK (Content-Type changes) func contentTypeChangeHighRisk(w http.ResponseWriter, r *http.Request) { - if os.Getenv("KEPLOY_MODE") == "test" { - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - w.Write([]byte("This is now plain text.")) - return - } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"message": "This is JSON."}`)) + response := map[string]interface{}{ + "message": "This is JSON.", + "timestamp": time.Now().Unix(), + } + json.NewEncoder(w).Encode(response) } -// HEADER: MEDIUM RISK (A non-critical header changes) func headerChangeMediumRisk(w http.ResponseWriter, r *http.Request) { - if os.Getenv("KEPLOY_MODE") == "test" { - w.Header().Set("X-Custom-Header", "new-value-987") - } else { - w.Header().Set("X-Custom-Header", "initial-value-123") + w.Header().Set("X-Custom-Header", "initial-value-123") + w.WriteHeader(http.StatusOK) + response := map[string]interface{}{ + "status": "header test", + "timestamp": time.Now().Unix(), } + json.NewEncoder(w).Encode(response) +} + +func statusBodyChange(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"status": "header test"}`)) + response := map[string]interface{}{ + "message": "Status and body not changed", + "timestamp": time.Now().UnixNano(), + } + json.NewEncoder(w).Encode(response) +} + +func headerBodyChange(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-Transaction-ID", "txn-1") + w.Header().Set("Content-Type", "application/json") + response := map[string]interface{}{ + "message": "Header and body not changed", + "timestamp": time.Now().UnixNano(), + } + json.NewEncoder(w).Encode(response) } -// NOISY: This should PASS if noise is configured correctly. -func noisyHeader(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Date", time.Now().UTC().Format(http.TimeFormat)) +func statusBodyHeaderChange(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-Transaction-ID", "txn-1") + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"message": "Check the Date header!"}`)) + response := map[string]interface{}{ + "message": "Status, body, and header not changed", + "timestamp": time.Now().UnixNano(), + } + json.NewEncoder(w).Encode(response) } func main() { @@ -163,7 +149,9 @@ func main() { http.HandleFunc("/status-change-high-risk", statusChangeHighRisk) http.HandleFunc("/content-type-change-high-risk", contentTypeChangeHighRisk) http.HandleFunc("/header-change-medium-risk", headerChangeMediumRisk) - http.HandleFunc("/noisy-header", noisyHeader) + http.HandleFunc("/status-body-change", statusBodyChange) + http.HandleFunc("/header-body-change", headerBodyChange) + http.HandleFunc("/status-body-header-change", statusBodyHeaderChange) port := "8080" log.Printf("Server starting on port %s...", port) if err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil); err != nil { From 598d33eb3caecd50952f4c4f1f67745aa14ac5e7 Mon Sep 17 00:00:00 2001 From: Anju Pathak Date: Sun, 12 Oct 2025 18:46:55 +0000 Subject: [PATCH 3/4] feat: make application changes to replicate risk profiles Signed-off-by: Anju Pathak --- risk-profile/main.go | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/risk-profile/main.go b/risk-profile/main.go index c13f193d..553b39f9 100644 --- a/risk-profile/main.go +++ b/risk-profile/main.go @@ -26,6 +26,7 @@ func getUsersLowRisk(w http.ResponseWriter, r *http.Request) { "name": user.Name, "email": user.Email, "timestamp": time.Now().Unix(), + "phone": "9999988888", } json.NewEncoder(w).Encode(response) } @@ -35,7 +36,7 @@ func getUsersMediumRisk(w http.ResponseWriter, r *http.Request) { user := originalUsers[0] response := map[string]interface{}{ "id": user.ID, - "name": user.Name, + "name": user.Name + "-Modified", "email": user.Email, "timestamp": time.Now().Unix(), } @@ -47,9 +48,10 @@ func getUsersMediumRiskWithAddition(w http.ResponseWriter, r *http.Request) { user := originalUsers[0] response := map[string]interface{}{ "id": user.ID, - "name": user.Name, + "name": user.Name + "-Modified", "email": user.Email, "timestamp": time.Now().Unix(), + "phone": "9999988888", } json.NewEncoder(w).Encode(response) } @@ -58,7 +60,7 @@ func getUsersHighRiskType(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") user := originalUsers[0] response := map[string]interface{}{ - "id": user.ID, + "id": "123", "name": user.Name, "email": user.Email, "timestamp": time.Now().Unix(), @@ -72,7 +74,6 @@ func getUsersHighRiskRemoval(w http.ResponseWriter, r *http.Request) { response := map[string]interface{}{ "id": user.ID, "name": user.Name, - "email": user.Email, "timestamp": time.Now().Unix(), } json.NewEncoder(w).Encode(response) @@ -80,7 +81,7 @@ func getUsersHighRiskRemoval(w http.ResponseWriter, r *http.Request) { func statusChangeHighRisk(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusAccepted) response := map[string]interface{}{ "status": "OK", "timestamp": time.Now().Unix(), @@ -89,7 +90,7 @@ func statusChangeHighRisk(w http.ResponseWriter, r *http.Request) { } func contentTypeChangeHighRisk(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") + w.Header().Set("Content-Type", "application/xml") w.WriteHeader(http.StatusOK) response := map[string]interface{}{ "message": "This is JSON.", @@ -99,7 +100,7 @@ func contentTypeChangeHighRisk(w http.ResponseWriter, r *http.Request) { } func headerChangeMediumRisk(w http.ResponseWriter, r *http.Request) { - w.Header().Set("X-Custom-Header", "initial-value-123") + w.Header().Set("X-Custom-Header", "initial-value-456") w.WriteHeader(http.StatusOK) response := map[string]interface{}{ "status": "header test", @@ -110,30 +111,30 @@ func headerChangeMediumRisk(w http.ResponseWriter, r *http.Request) { func statusBodyChange(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusAccepted) response := map[string]interface{}{ - "message": "Status and body not changed", + "message": "Status and body changed", "timestamp": time.Now().UnixNano(), } json.NewEncoder(w).Encode(response) } func headerBodyChange(w http.ResponseWriter, r *http.Request) { - w.Header().Set("X-Transaction-ID", "txn-1") + w.Header().Set("X-Transaction-ID", "txn-2") w.Header().Set("Content-Type", "application/json") response := map[string]interface{}{ - "message": "Header and body not changed", + "message": "Header and body changed", "timestamp": time.Now().UnixNano(), } json.NewEncoder(w).Encode(response) } func statusBodyHeaderChange(w http.ResponseWriter, r *http.Request) { - w.Header().Set("X-Transaction-ID", "txn-1") + w.Header().Set("X-Transaction-ID", "txn-2") w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusAccepted) response := map[string]interface{}{ - "message": "Status, body, and header not changed", + "message": "Status, body, and header changed", "timestamp": time.Now().UnixNano(), } json.NewEncoder(w).Encode(response) From e2b6b833085dd2721944dd194e408e78d0021fab Mon Sep 17 00:00:00 2001 From: Anju Pathak Date: Sun, 12 Oct 2025 19:01:25 +0000 Subject: [PATCH 4/4] feat: add endpoint for schema compeletly changed Signed-off-by: Anju Pathak --- risk-profile/curl.sh | 1 + risk-profile/main.go | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/risk-profile/curl.sh b/risk-profile/curl.sh index b7df611e..c4d6bd98 100644 --- a/risk-profile/curl.sh +++ b/risk-profile/curl.sh @@ -4,6 +4,7 @@ curl http://localhost:8080/users-medium-risk curl http://localhost:8080/users-medium-risk-with-addition curl http://localhost:8080/users-high-risk-type curl http://localhost:8080/users-high-risk-removal +curl http://localhost:8080/schema-completely-changed # Status and Header tests curl http://localhost:8080/status-change-high-risk diff --git a/risk-profile/main.go b/risk-profile/main.go index 553b39f9..53a3f75e 100644 --- a/risk-profile/main.go +++ b/risk-profile/main.go @@ -140,6 +140,12 @@ func statusBodyHeaderChange(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(response) } +func schemaCompletelyChanged(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + fmt.Fprintln(w, "This is a completely different, non-JSON response body.") +} + func main() { log.Println("Application starting...") http.HandleFunc("/users-low-risk", getUsersLowRisk) @@ -153,6 +159,7 @@ func main() { http.HandleFunc("/status-body-change", statusBodyChange) http.HandleFunc("/header-body-change", headerBodyChange) http.HandleFunc("/status-body-header-change", statusBodyHeaderChange) + http.HandleFunc("/schema-completely-changed", schemaCompletelyChanged) port := "8080" log.Printf("Server starting on port %s...", port) if err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil); err != nil {