From b32f836bddb341e813adbe12632a2cf9d9d7c638 Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Wed, 23 Jul 2025 19:06:12 -0600 Subject: [PATCH 1/2] Update N+ license handling to reflect reporting section presence Previously there was an assumption that `/api/9/license` will always include the `reporting` subtree, and if it was absent, related values were populated with zeroes [1] making the whole struct hard to rely on. With this change, NginxLicense.Reporting will be a pointer to the original subtree if it present in the API response, or nil otherwise. [1] https://betterstack.com/community/guides/scaling-go/json-in-go/#2-missing-fields-fallback-to-zero-values --- client/nginx.go | 2 +- client/nginx_test.go | 137 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/client/nginx.go b/client/nginx.go index ebe59212..7f0b6f31 100644 --- a/client/nginx.go +++ b/client/nginx.go @@ -227,7 +227,7 @@ type LicenseReporting struct { type NginxLicense struct { ActiveTill uint64 `json:"active_till"` Eval bool - Reporting LicenseReporting + Reporting *LicenseReporting } // Caches is a map of cache stats by cache zone. diff --git a/client/nginx_test.go b/client/nginx_test.go index 6d1661a0..33e6416c 100644 --- a/client/nginx_test.go +++ b/client/nginx_test.go @@ -1503,6 +1503,143 @@ func TestInternalError(t *testing.T) { } } +func TestLicenseWithReporting(t *testing.T) { + t.Parallel() + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.RequestURI == "/": + _, err := w.Write([]byte(`[1,2,3,4,5,6,7,8,9]`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + case r.RequestURI == "/9/": + _, err := w.Write([]byte(`["nginx","processes","connections","slabs","http","resolvers","ssl","license","workers"]`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + case strings.HasPrefix(r.RequestURI, "/9/nginx"): + _, err := w.Write([]byte(`{ + "version": "1.29.0", + "build": "nginx-plus-r34" + }`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + case strings.HasPrefix(r.RequestURI, "/9/license"): + _, err := w.Write([]byte(`{ + "active_till" : 428250000, + "eval": false, + "reporting": { + "healthy": true, + "fails": 42, + "grace": 86400 + } + }`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + default: + _, err := w.Write([]byte(`{}`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } + })) + defer ts.Close() + + client, err := NewNginxClient(ts.URL, WithAPIVersion(9), WithCheckAPI()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if client == nil { + t.Fatalf("client is nil") + } + + license, err := client.GetNginxLicense(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + testReporting := LicenseReporting{ + Healthy: true, + Fails: 42, + Grace: 86400, + } + + testLicense := NginxLicense{ + ActiveTill: 428250000, + Eval: false, + Reporting: &testReporting, + } + + if !reflect.DeepEqual(license, &testLicense) { + t.Fatalf("NGINX license: expected %v, actual %v; NGINX reporting: expected %v, actual %v", testLicense, license, testReporting, license.Reporting) + } +} + +func TestLicenseWithoutReporting(t *testing.T) { + t.Parallel() + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.RequestURI == "/": + _, err := w.Write([]byte(`[1,2,3,4,5,6,7,8,9]`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + case r.RequestURI == "/9/": + _, err := w.Write([]byte(`["nginx","processes","connections","slabs","http","resolvers","ssl","license","workers"]`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + case strings.HasPrefix(r.RequestURI, "/9/nginx"): + _, err := w.Write([]byte(`{ + "version": "1.29.0", + "build": "nginx-plus-r34" + }`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + case strings.HasPrefix(r.RequestURI, "/9/license"): + _, err := w.Write([]byte(`{ + "active_till" : 428250000, + "eval": false + }`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + default: + _, err := w.Write([]byte(`{}`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } + })) + defer ts.Close() + + client, err := NewNginxClient(ts.URL, WithAPIVersion(9), WithCheckAPI()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if client == nil { + t.Fatalf("client is nil") + } + + license, err := client.GetNginxLicense(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + testLicense := NginxLicense{ + ActiveTill: 428250000, + Eval: false, + Reporting: nil, + } + + if !reflect.DeepEqual(license, &testLicense) { + t.Fatalf("NGINX license: expected %v, actual %v", testLicense, license) + } +} + type response struct { servers interface{} statusCode int From 7ad354dc140a80a31ca66657e9b76e846b213db9 Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Thu, 24 Jul 2025 18:45:05 -0600 Subject: [PATCH 2/2] Fix alignment issue reported by govet --- client/nginx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/nginx.go b/client/nginx.go index 7f0b6f31..e6c9c0ad 100644 --- a/client/nginx.go +++ b/client/nginx.go @@ -225,9 +225,9 @@ type LicenseReporting struct { // NginxLicense contains licensing information about NGINX Plus. type NginxLicense struct { + Reporting *LicenseReporting ActiveTill uint64 `json:"active_till"` Eval bool - Reporting *LicenseReporting } // Caches is a map of cache stats by cache zone.