2026-07-27 01:11:38 +08:00
|
|
|
package routers
|
|
|
|
|
|
|
|
|
|
import (
|
2026-07-27 02:03:26 +08:00
|
|
|
"net/http"
|
2026-07-27 01:11:38 +08:00
|
|
|
"testing"
|
|
|
|
|
|
2026-07-27 11:00:21 +08:00
|
|
|
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/platform"
|
2026-07-27 01:11:38 +08:00
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
)
|
|
|
|
|
|
2026-07-27 11:00:21 +08:00
|
|
|
func TestEveryContractHasRegisteredRoute(t *testing.T) {
|
|
|
|
|
engine := gin.New()
|
|
|
|
|
RegisterPlatform("heqi", engine)
|
|
|
|
|
routes := make(map[string]map[string]bool)
|
|
|
|
|
for _, route := range engine.Routes() {
|
|
|
|
|
if routes[route.Path] == nil {
|
|
|
|
|
routes[route.Path] = make(map[string]bool)
|
|
|
|
|
}
|
|
|
|
|
routes[route.Path][route.Method] = true
|
|
|
|
|
}
|
|
|
|
|
for _, contract := range platform.ExpectedResources() {
|
|
|
|
|
path := "/heqi/platform/v1" + contract.Path
|
|
|
|
|
switch contract.Mode {
|
|
|
|
|
case platform.ReadOnly:
|
|
|
|
|
assertRouteMethods(t, routes, path, http.MethodGet)
|
|
|
|
|
assertRouteMethods(t, routes, path+"/:identity", http.MethodGet)
|
2026-07-27 11:13:55 +08:00
|
|
|
assertNoRouteMethods(t, routes, path, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
assertNoRouteMethods(t, routes, path+"/:identity", http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
2026-07-27 11:00:21 +08:00
|
|
|
case platform.AppendOnly:
|
2026-07-27 11:13:55 +08:00
|
|
|
assertRouteMethods(t, routes, path, http.MethodGet, http.MethodPost)
|
|
|
|
|
assertNoRouteMethods(t, routes, path, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
2026-07-28 08:44:54 +08:00
|
|
|
case platform.Editable:
|
|
|
|
|
assertRouteMethods(t, routes, path, http.MethodGet, http.MethodPost)
|
|
|
|
|
assertRouteMethods(t, routes, path+"/:identity", http.MethodGet, http.MethodPut)
|
|
|
|
|
assertRouteMethods(t, routes, path+"/:identity/status", http.MethodPatch)
|
|
|
|
|
assertNoRouteMethods(t, routes, path, http.MethodDelete)
|
|
|
|
|
assertNoRouteMethods(t, routes, path+"/:identity", http.MethodDelete)
|
2026-07-28 10:42:26 +08:00
|
|
|
case platform.Managed:
|
|
|
|
|
assertRouteMethods(t, routes, path, http.MethodGet, http.MethodPost)
|
|
|
|
|
assertRouteMethods(t, routes, path+"/:identity", http.MethodGet, http.MethodPut)
|
|
|
|
|
assertNoRouteMethods(t, routes, path, http.MethodDelete)
|
|
|
|
|
assertNoRouteMethods(t, routes, path+"/:identity", http.MethodPatch, http.MethodDelete)
|
2026-07-27 11:00:21 +08:00
|
|
|
default:
|
|
|
|
|
assertRouteMethods(t, routes, path, http.MethodGet, http.MethodPost)
|
|
|
|
|
assertRouteMethods(t, routes, path+"/:identity", http.MethodGet, http.MethodPut, http.MethodDelete)
|
|
|
|
|
assertRouteMethods(t, routes, path+"/:identity/status", http.MethodPatch)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-27 01:11:38 +08:00
|
|
|
func TestPlatformGasRouteUsesGasBasic(t *testing.T) {
|
|
|
|
|
engine := gin.New()
|
|
|
|
|
RegisterPlatform("heqi", engine)
|
|
|
|
|
|
|
|
|
|
for _, route := range engine.Routes() {
|
|
|
|
|
if route.Path == "/heqi/platform/v1/gas/gas_station" {
|
|
|
|
|
t.Fatal("gas station route must use gas_basic as its resource name")
|
|
|
|
|
}
|
2026-07-28 10:42:26 +08:00
|
|
|
if route.Method == "GET" && route.Path == "/heqi/platform/v1/gas_basic" {
|
2026-07-27 01:11:38 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
t.Fatal("gas_basic list route is not registered")
|
|
|
|
|
}
|
2026-07-27 02:03:26 +08:00
|
|
|
|
|
|
|
|
func TestPlatformOrganizationAndAccountRoutesExposeResourceCRUD(t *testing.T) {
|
|
|
|
|
engine := gin.New()
|
|
|
|
|
RegisterPlatform("heqi", engine)
|
|
|
|
|
|
|
|
|
|
routes := make(map[string]map[string]bool)
|
|
|
|
|
for _, route := range engine.Routes() {
|
|
|
|
|
if routes[route.Path] == nil {
|
|
|
|
|
routes[route.Path] = make(map[string]bool)
|
|
|
|
|
}
|
|
|
|
|
routes[route.Path][route.Method] = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, resource := range []string{
|
2026-07-28 10:42:26 +08:00
|
|
|
"/gas_basic",
|
|
|
|
|
"/gas_account",
|
|
|
|
|
"/delivery_basic",
|
|
|
|
|
"/delivery_account",
|
|
|
|
|
"/staff_account",
|
|
|
|
|
"/staff_credential",
|
|
|
|
|
"/user_account",
|
|
|
|
|
"/user_address",
|
|
|
|
|
"/user_service_relation",
|
2026-07-28 13:35:59 +08:00
|
|
|
"/platform_account",
|
2026-07-28 10:42:26 +08:00
|
|
|
"/platform_role",
|
2026-07-27 02:03:26 +08:00
|
|
|
} {
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1"+resource, http.MethodGet, http.MethodPost)
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1"+resource+"/:identity", http.MethodGet, http.MethodPut, http.MethodDelete)
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1"+resource+"/:identity/status", http.MethodPatch)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-29 13:18:52 +08:00
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1/platform_menu", http.MethodGet)
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1/platform_menu/:identity", http.MethodGet)
|
|
|
|
|
assertNoRouteMethods(t, routes, "/heqi/platform/v1/platform_menu", http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
assertNoRouteMethods(t, routes, "/heqi/platform/v1/platform_menu/:identity", http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
2026-07-28 10:42:26 +08:00
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1/platform_role/:identity/menu", http.MethodGet, http.MethodPut)
|
2026-07-28 13:35:59 +08:00
|
|
|
assertNoRouteMethods(t, routes, "/heqi/platform/v1/platform_role/:identity/menus", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
assertNoRouteMethods(t, routes, "/heqi/platform/v1/platfrom_account", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
2026-07-27 02:03:26 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-28 08:44:54 +08:00
|
|
|
func TestPlatformProductCommerceAndDeliveryRoutesFollowTheirContracts(t *testing.T) {
|
2026-07-27 02:47:39 +08:00
|
|
|
engine := gin.New()
|
|
|
|
|
RegisterPlatform("heqi", engine)
|
|
|
|
|
|
|
|
|
|
routes := make(map[string]map[string]bool)
|
|
|
|
|
for _, route := range engine.Routes() {
|
|
|
|
|
if routes[route.Path] == nil {
|
|
|
|
|
routes[route.Path] = make(map[string]bool)
|
|
|
|
|
}
|
|
|
|
|
routes[route.Path][route.Method] = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, resource := range []string{
|
2026-07-29 14:25:38 +08:00
|
|
|
"/ec_category", "/ec_product", "/ec_product_attribute", "/ec_product_image",
|
2026-07-27 02:47:39 +08:00
|
|
|
} {
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1"+resource, http.MethodGet, http.MethodPost)
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1"+resource+"/:identity", http.MethodGet, http.MethodPut, http.MethodDelete)
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1"+resource+"/:identity/status", http.MethodPatch)
|
|
|
|
|
}
|
2026-07-29 14:25:38 +08:00
|
|
|
for _, resource := range []string{"/ec_cart", "/ec_order", "/ec_order_item", "/ec_review"} {
|
|
|
|
|
path := "/heqi/platform/v1" + resource
|
|
|
|
|
assertRouteMethods(t, routes, path, http.MethodGet)
|
|
|
|
|
assertRouteMethods(t, routes, path+"/:identity", http.MethodGet)
|
|
|
|
|
assertNoRouteMethods(t, routes, path, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
assertNoRouteMethods(t, routes, path+"/:identity", http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
}
|
2026-07-27 02:47:39 +08:00
|
|
|
|
2026-07-28 10:42:26 +08:00
|
|
|
for _, resource := range []string{"/gasorder_item", "/gasorder_assign", "/gasorder_status", "/gasorder_track", "/gasorder_track_point", "/gasorder_confirm", "/gasorder_payment", "/gasorder_contract_revision"} {
|
|
|
|
|
path := "/heqi/platform/v1" + resource
|
|
|
|
|
assertRouteMethods(t, routes, path, http.MethodGet)
|
|
|
|
|
assertRouteMethods(t, routes, path+"/:identity", http.MethodGet)
|
|
|
|
|
assertNoRouteMethods(t, routes, path, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
assertNoRouteMethods(t, routes, path+"/:identity", http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
}
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1/gasorder_contract", http.MethodGet, http.MethodPost)
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1/gasorder_contract/:identity", http.MethodGet, http.MethodPut)
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1/gasorder_basic", http.MethodGet, http.MethodPost)
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1/gasorder_basic/:identity", http.MethodGet)
|
|
|
|
|
for _, action := range []string{"assign", "filling", "ready", "exception", "recover", "cancel"} {
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1/gasorder_basic/:identity/"+action, http.MethodPost)
|
|
|
|
|
}
|
|
|
|
|
for _, old := range []string{"/delivery/delivery_task", "/delivery/delivery_track", "/delivery/delivery_track_point"} {
|
|
|
|
|
assertNoRouteMethods(t, routes, "/heqi/platform/v1"+old, http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
}
|
2026-07-27 12:02:08 +08:00
|
|
|
|
2026-07-28 08:44:54 +08:00
|
|
|
for _, resource := range []string{"/product_type", "/product_warehouse", "/product_info", "/product_repair"} {
|
|
|
|
|
path := "/heqi/platform/v1" + resource
|
|
|
|
|
assertRouteMethods(t, routes, path, http.MethodGet, http.MethodPost)
|
|
|
|
|
assertRouteMethods(t, routes, path+"/:identity", http.MethodGet, http.MethodPut)
|
|
|
|
|
assertRouteMethods(t, routes, path+"/:identity/status", http.MethodPatch)
|
|
|
|
|
assertNoRouteMethods(t, routes, path+"/:identity", http.MethodDelete)
|
2026-07-27 02:47:39 +08:00
|
|
|
}
|
2026-07-28 08:44:54 +08:00
|
|
|
owner := "/heqi/platform/v1/product_owner"
|
2026-07-29 14:03:10 +08:00
|
|
|
assertRouteMethods(t, routes, owner, http.MethodGet)
|
|
|
|
|
assertNoRouteMethods(t, routes, owner, http.MethodPost)
|
2026-07-28 08:44:54 +08:00
|
|
|
assertRouteMethods(t, routes, owner+"/:identity", http.MethodGet)
|
|
|
|
|
assertNoRouteMethods(t, routes, owner+"/:identity", http.MethodPut, http.MethodPatch, http.MethodDelete)
|
2026-07-27 02:47:39 +08:00
|
|
|
|
2026-07-27 15:49:51 +08:00
|
|
|
for _, resource := range []string{
|
|
|
|
|
"/safety/safe_rule", "/safety/safe_event", "/safety/safe_inspection",
|
|
|
|
|
"/safety/safe_event/:identity/disposals",
|
|
|
|
|
} {
|
2026-07-27 14:16:39 +08:00
|
|
|
path := "/heqi/platform/v1" + resource
|
|
|
|
|
assertNoRouteMethods(t, routes, path, http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
assertNoRouteMethods(t, routes, path+"/:identity", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
assertNoRouteMethods(t, routes, path+"/:identity/status", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
}
|
2026-07-27 02:47:39 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-27 15:31:22 +08:00
|
|
|
func TestPlatformFinanceContentRoutesFollowTheirContracts(t *testing.T) {
|
2026-07-27 03:18:41 +08:00
|
|
|
engine := gin.New()
|
|
|
|
|
RegisterPlatform("heqi", engine)
|
|
|
|
|
|
|
|
|
|
routes := make(map[string]map[string]bool)
|
|
|
|
|
for _, route := range engine.Routes() {
|
|
|
|
|
if routes[route.Path] == nil {
|
|
|
|
|
routes[route.Path] = make(map[string]bool)
|
|
|
|
|
}
|
|
|
|
|
routes[route.Path][route.Method] = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, resource := range []string{
|
2026-07-29 14:25:38 +08:00
|
|
|
"/fin_settlement", "/cms_content", "/cs_ticket",
|
2026-07-27 03:18:41 +08:00
|
|
|
} {
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1"+resource, http.MethodGet, http.MethodPost)
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1"+resource+"/:identity", http.MethodGet, http.MethodPut, http.MethodDelete)
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1"+resource+"/:identity/status", http.MethodPatch)
|
|
|
|
|
}
|
2026-07-29 14:25:38 +08:00
|
|
|
for _, resource := range []string{"/fin_payment", "/fin_reconciliation"} {
|
|
|
|
|
path := "/heqi/platform/v1" + resource
|
|
|
|
|
assertRouteMethods(t, routes, path, http.MethodGet)
|
|
|
|
|
assertRouteMethods(t, routes, path+"/:identity", http.MethodGet)
|
|
|
|
|
assertNoRouteMethods(t, routes, path, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
assertNoRouteMethods(t, routes, path+"/:identity", http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
}
|
2026-07-27 03:18:41 +08:00
|
|
|
|
|
|
|
|
for _, resource := range []string{
|
2026-07-28 09:25:49 +08:00
|
|
|
"/wallet_basic", "/wallet_bank", "/wallet_payment", "/wallet_record", "/wallet_refund", "/wallet_apply_cash",
|
2026-07-27 03:18:41 +08:00
|
|
|
} {
|
|
|
|
|
path := "/heqi/platform/v1" + resource
|
|
|
|
|
assertRouteMethods(t, routes, path, http.MethodGet)
|
|
|
|
|
assertRouteMethods(t, routes, path+"/:identity", http.MethodGet)
|
|
|
|
|
for _, method := range []string{http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete} {
|
|
|
|
|
if routes[path][method] || routes[path+"/:identity"][method] {
|
|
|
|
|
t.Errorf("read-only resource %s unexpectedly permits %s", resource, method)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-28 09:25:49 +08:00
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1/wallet_basic/:identity/status", http.MethodPatch)
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1/wallet_basic/:identity/recharge", http.MethodPost)
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1/wallet_basic/owner/:owner_type/:owner_identity", http.MethodGet)
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1/wallet_apply_cash/:identity/approve", http.MethodPost)
|
|
|
|
|
assertRouteMethods(t, routes, "/heqi/platform/v1/wallet_apply_cash/:identity/reject", http.MethodPost)
|
|
|
|
|
for _, oldPath := range []string{
|
|
|
|
|
"/heqi/platform/v1/wallet/wallet",
|
|
|
|
|
"/heqi/platform/v1/wallet/wallet_ledger",
|
|
|
|
|
"/heqi/platform/v1/wallet/wallet_recharge",
|
|
|
|
|
"/heqi/platform/v1/wallet/wallet_withdrawal",
|
|
|
|
|
} {
|
|
|
|
|
assertNoRouteMethods(t, routes, oldPath, http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
}
|
2026-07-27 03:18:41 +08:00
|
|
|
|
2026-07-27 15:31:22 +08:00
|
|
|
for _, resource := range []string{
|
|
|
|
|
"/content/cnt_content", "/notification/ntf_template",
|
|
|
|
|
"/report/report", "/report/report_item", "/report/report_metric_snapshot",
|
|
|
|
|
"/audit/audit_operation_log", "/audit/audit_export_log", "/audit/audit_approval",
|
|
|
|
|
} {
|
2026-07-27 14:16:39 +08:00
|
|
|
path := "/heqi/platform/v1" + resource
|
|
|
|
|
assertNoRouteMethods(t, routes, path, http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
assertNoRouteMethods(t, routes, path+"/:identity", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
|
|
|
|
}
|
2026-07-27 15:31:22 +08:00
|
|
|
assertNoRouteMethods(t, routes, "/heqi/platform/v1/audit/audit_approval/:identity/approve", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
2026-07-27 03:18:41 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-27 02:03:26 +08:00
|
|
|
func assertRouteMethods(t *testing.T, routes map[string]map[string]bool, path string, methods ...string) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
for _, method := range methods {
|
|
|
|
|
if !routes[path][method] {
|
|
|
|
|
t.Errorf("route %s %s is not registered", method, path)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-27 11:13:55 +08:00
|
|
|
|
|
|
|
|
func assertNoRouteMethods(t *testing.T, routes map[string]map[string]bool, path string, methods ...string) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
for _, method := range methods {
|
|
|
|
|
if routes[path][method] {
|
|
|
|
|
t.Errorf("route %s %s must not be registered", method, path)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|