// Copyright 2026 Moko Consulting // SPDX-License-Identifier: GPL-3.0-or-later package integration import ( "fmt" "net/http" "strings" "testing" auth_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth" repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unittest" user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user" api "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/tests" "github.com/stretchr/testify/assert" ) func TestAPILicensePackages(t *testing.T) { defer tests.PrepareTestEnv(t)() repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) token := getUserToken(t, user.LowerName, auth_model.AccessTokenScopeWriteRepository) urlPrefix := fmt.Sprintf("/api/v1/repos/%s/%s", user.Name, repo.Name) t.Run("ListPackages", func(t *testing.T) { req := NewRequest(t, "GET", urlPrefix+"/license-packages").AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var packages []*api.LicensePackage DecodeJSON(t, resp, &packages) // Initially empty (master may be auto-created on web visit, but not via API list). }) t.Run("CreatePackage", func(t *testing.T) { body := `{"name":"Test Pro Annual","description":"Annual pro subscription","duration_days":365,"max_sites":5}` req := NewRequestWithBody(t, "POST", urlPrefix+"/license-packages", strings.NewReader(body)). AddTokenAuth(token). SetHeader("Content-Type", "application/json") resp := MakeRequest(t, req, http.StatusCreated) var pkg api.LicensePackage DecodeJSON(t, resp, &pkg) assert.Equal(t, "Test Pro Annual", pkg.Name) assert.Equal(t, 365, pkg.DurationDays) assert.Equal(t, 5, pkg.MaxSites) assert.True(t, pkg.IsActive) assert.True(t, pkg.ID > 0) }) t.Run("CreatePackageNoName", func(t *testing.T) { body := `{"description":"Missing name"}` req := NewRequestWithBody(t, "POST", urlPrefix+"/license-packages", strings.NewReader(body)). AddTokenAuth(token). SetHeader("Content-Type", "application/json") MakeRequest(t, req, http.StatusUnprocessableEntity) }) } func TestAPILicenseKeys(t *testing.T) { defer tests.PrepareTestEnv(t)() repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) token := getUserToken(t, user.LowerName, auth_model.AccessTokenScopeWriteRepository) urlPrefix := fmt.Sprintf("/api/v1/repos/%s/%s", user.Name, repo.Name) // Create a package first. body := `{"name":"Test Package","duration_days":30}` req := NewRequestWithBody(t, "POST", urlPrefix+"/license-packages", strings.NewReader(body)). AddTokenAuth(token). SetHeader("Content-Type", "application/json") resp := MakeRequest(t, req, http.StatusCreated) var pkg api.LicensePackage DecodeJSON(t, resp, &pkg) var createdKeyID int64 var rawKey string t.Run("CreateKey", func(t *testing.T) { body := fmt.Sprintf(`{"package_id":%d,"licensee_name":"John Doe","licensee_email":"john@example.com"}`, pkg.ID) req := NewRequestWithBody(t, "POST", urlPrefix+"/license-keys", strings.NewReader(body)). AddTokenAuth(token). SetHeader("Content-Type", "application/json") resp := MakeRequest(t, req, http.StatusCreated) var key api.LicenseKeyCreated DecodeJSON(t, resp, &key) assert.NotEmpty(t, key.RawKey) assert.Contains(t, key.RawKey, "MOKO-") assert.Equal(t, "John Doe", key.LicenseeName) assert.True(t, key.IsActive) createdKeyID = key.ID rawKey = key.RawKey }) t.Run("ListKeys", func(t *testing.T) { req := NewRequest(t, "GET", urlPrefix+"/license-keys").AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var keys []*api.LicenseKey DecodeJSON(t, resp, &keys) assert.GreaterOrEqual(t, len(keys), 1) }) t.Run("EditKey", func(t *testing.T) { body := `{"licensee_name":"Jane Doe","domain_restriction":"example.com,test.com"}` req := NewRequestWithBody(t, "PATCH", fmt.Sprintf("%s/license-keys/%d", urlPrefix, createdKeyID), strings.NewReader(body)). AddTokenAuth(token). SetHeader("Content-Type", "application/json") resp := MakeRequest(t, req, http.StatusOK) var key api.LicenseKey DecodeJSON(t, resp, &key) assert.Equal(t, "Jane Doe", key.LicenseeName) }) t.Run("RenewKey", func(t *testing.T) { req := NewRequest(t, "POST", fmt.Sprintf("%s/license-keys/%d/renew", urlPrefix, createdKeyID)). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var key api.LicenseKey DecodeJSON(t, resp, &key) assert.NotNil(t, key.ExpiresAt) }) t.Run("ValidateKey", func(t *testing.T) { body := fmt.Sprintf(`{"key":"%s","domain":"example.com"}`, rawKey) req := NewRequestWithBody(t, "POST", urlPrefix+"/license-keys/validate", strings.NewReader(body)). SetHeader("Content-Type", "application/json") // Note: no token — this is a public endpoint. resp := MakeRequest(t, req, http.StatusOK) var result api.ValidateLicenseKeyResponse DecodeJSON(t, resp, &result) assert.True(t, result.Valid) assert.Equal(t, "Test Package", result.PackageName) }) t.Run("ValidateInvalidKey", func(t *testing.T) { body := `{"key":"MOKO-XXXX-XXXX-XXXX-XXXX","domain":"example.com"}` req := NewRequestWithBody(t, "POST", urlPrefix+"/license-keys/validate", strings.NewReader(body)). SetHeader("Content-Type", "application/json") resp := MakeRequest(t, req, http.StatusOK) var result api.ValidateLicenseKeyResponse DecodeJSON(t, resp, &result) assert.False(t, result.Valid) }) t.Run("DeleteKey", func(t *testing.T) { req := NewRequest(t, "DELETE", fmt.Sprintf("%s/license-keys/%d", urlPrefix, createdKeyID)). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) }) } func TestAPILicensePurchaseWebhook(t *testing.T) { defer tests.PrepareTestEnv(t)() repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) token := getUserToken(t, user.LowerName, auth_model.AccessTokenScopeWriteRepository) urlPrefix := fmt.Sprintf("/api/v1/repos/%s/%s", user.Name, repo.Name) // Create a package. body := `{"name":"Purchase Test","duration_days":90}` req := NewRequestWithBody(t, "POST", urlPrefix+"/license-packages", strings.NewReader(body)). AddTokenAuth(token). SetHeader("Content-Type", "application/json") resp := MakeRequest(t, req, http.StatusCreated) var pkg api.LicensePackage DecodeJSON(t, resp, &pkg) t.Run("PurchaseNewKey", func(t *testing.T) { body := fmt.Sprintf(`{"package_id":%d,"licensee_name":"Buyer","licensee_email":"buyer@shop.com","domain":"shop.com","payment_ref":"stripe_pi_test123"}`, pkg.ID) req := NewRequestWithBody(t, "POST", urlPrefix+"/license-keys/purchase", strings.NewReader(body)). AddTokenAuth(token). SetHeader("Content-Type", "application/json") resp := MakeRequest(t, req, http.StatusCreated) var key api.LicenseKeyCreated DecodeJSON(t, resp, &key) assert.NotEmpty(t, key.RawKey) assert.Equal(t, "Buyer", key.LicenseeName) }) t.Run("PurchaseIdempotent", func(t *testing.T) { // Same payment_ref should return existing key without raw_key. body := fmt.Sprintf(`{"package_id":%d,"licensee_name":"Buyer","payment_ref":"stripe_pi_test123"}`, pkg.ID) req := NewRequestWithBody(t, "POST", urlPrefix+"/license-keys/purchase", strings.NewReader(body)). AddTokenAuth(token). SetHeader("Content-Type", "application/json") resp := MakeRequest(t, req, http.StatusOK) var key api.LicenseKeyCreated DecodeJSON(t, resp, &key) assert.Empty(t, key.RawKey) // Raw key not available on idempotent return. }) }