244 lines
8.2 KiB
Go
244 lines
8.2 KiB
Go
package common
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.apinb.com/bsm-sdk/core/types"
|
|
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func TestFilterFieldsKeepsOnlyAllowedKeys(t *testing.T) {
|
|
got := FilterFields(map[string]any{"name": "n", "password_hash": "x"}, []string{"name"})
|
|
if len(got) != 1 || got["name"] != "n" {
|
|
t.Fatalf("unexpected filtered fields: %#v", got)
|
|
}
|
|
}
|
|
|
|
func TestStripClientManagedCreateFields(t *testing.T) {
|
|
input := map[string]any{"id": float64(99), "identity": "client-value", "name": "气站"}
|
|
stripClientManagedCreateFields(input)
|
|
if _, exists := input["id"]; exists {
|
|
t.Fatal("client supplied database ID was retained")
|
|
}
|
|
if _, exists := input["identity"]; exists {
|
|
t.Fatal("client supplied identity was retained")
|
|
}
|
|
if input["name"] != "气站" {
|
|
t.Fatalf("business fields changed: %#v", input)
|
|
}
|
|
}
|
|
|
|
func TestNewEntityGeneratesUUIDV7Identity(t *testing.T) {
|
|
first := NewEntity(StatusDraft)
|
|
second := NewEntity(StatusDraft)
|
|
if first.Identity == second.Identity {
|
|
t.Fatal("generated identities must be unique")
|
|
}
|
|
parsed, err := uuid.Parse(first.Identity)
|
|
if err != nil || parsed.Version() != 7 {
|
|
t.Fatalf("identity = %q, want UUID V7", first.Identity)
|
|
}
|
|
}
|
|
|
|
func TestOperationalQueriesExcludeArchivedRecords(t *testing.T) {
|
|
sqlDatabase, _, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer sqlDatabase.Close()
|
|
database, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDatabase}), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
statement := database.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
|
return ActiveRecords(tx.Model(&models.EcProduct{})).Find(&[]models.EcProduct{})
|
|
})
|
|
if !strings.Contains(statement, `"ec_product"."status" <> 3`) {
|
|
t.Fatalf("archive filter missing from operational query: %s", statement)
|
|
}
|
|
}
|
|
|
|
func TestOperationalQueriesQualifyStatusAcrossJoins(t *testing.T) {
|
|
sqlDatabase, _, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer sqlDatabase.Close()
|
|
database, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDatabase}), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
statement := database.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
|
return ActiveRecords(tx.Model(&models.StaffCredential{})).
|
|
Joins("JOIN staff_account ON staff_account.id = staff_credential.staff_account_id").
|
|
Find(&[]models.StaffCredential{})
|
|
})
|
|
if !strings.Contains(statement, `"staff_credential"."status" <> 3`) {
|
|
t.Fatalf("joined archive filter is not table-qualified: %s", statement)
|
|
}
|
|
if strings.Contains(statement, `WHERE status <>`) {
|
|
t.Fatalf("joined archive filter is ambiguous: %s", statement)
|
|
}
|
|
}
|
|
|
|
func TestResourceResponseStripsInternalIDsRecursively(t *testing.T) {
|
|
got := ResourceResponse(map[string]any{
|
|
"id": uint64(1), "identity": "root",
|
|
"child": map[string]any{"gasorder_basic_id": uint64(2), "identity": "child"},
|
|
}).(map[string]any)
|
|
if _, exists := got["id"]; exists {
|
|
t.Fatal("root database ID was exposed")
|
|
}
|
|
child := got["child"].(map[string]any)
|
|
if _, exists := child["gasorder_basic_id"]; exists {
|
|
t.Fatal("relation database ID was exposed")
|
|
}
|
|
}
|
|
|
|
func TestPublicResourceResponsePreservesOnlyRecordID(t *testing.T) {
|
|
got, err := PublicResourceResponse(map[string]any{
|
|
"id": uint64(7), "identity": "record",
|
|
"child": map[string]any{"id": uint64(8), "identity": "child"},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
record := got.(map[string]any)
|
|
if record["id"] != float64(7) {
|
|
t.Fatalf("record ID = %#v, want 7", record["id"])
|
|
}
|
|
if _, exists := record["child"].(map[string]any)["id"]; exists {
|
|
t.Fatal("nested database ID was exposed")
|
|
}
|
|
}
|
|
|
|
func TestPublicResourceResponsePreservesListRecordIDs(t *testing.T) {
|
|
got, err := PublicResourceResponse([]map[string]any{
|
|
{"id": uint64(11), "identity": "first"},
|
|
{"id": uint64(12), "identity": "second"},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
list := got.([]any)
|
|
if list[0].(map[string]any)["id"] != float64(11) ||
|
|
list[1].(map[string]any)["id"] != float64(12) {
|
|
t.Fatalf("list record IDs were not preserved: %#v", list)
|
|
}
|
|
}
|
|
|
|
func TestPublicFieldProtectionMasksGasorderContacts(t *testing.T) {
|
|
value := map[string]any{"contact_name": "张三", "contact_phone": "13800138000"}
|
|
ProtectPublicFields(value, false, false, false)
|
|
if _, exists := value["contact_name"]; exists {
|
|
t.Fatal("contact name remains public")
|
|
}
|
|
if _, exists := value["contact_phone"]; exists {
|
|
t.Fatal("contact phone remains public")
|
|
}
|
|
}
|
|
|
|
func TestPlatformAdminResourceResponseRetainsNamesAndPrimaryPhone(t *testing.T) {
|
|
ctx, _ := gin.CreateTestContext(nil)
|
|
ctx.Set("Auth", &types.JwtClaims{Client: "platform_admin"})
|
|
|
|
user := map[string]any{
|
|
"name": "张三", "real_name": "张三", "phone": "13800138000",
|
|
"address": "敏感地址", "contact_phone": "13900139000",
|
|
}
|
|
ProtectPreciseLocation(ctx, &models.UserAccount{}, user)
|
|
if user["name"] != "张三" || user["real_name"] != "张三" || user["phone"] != "13800138000" {
|
|
t.Fatalf("platform personal data was masked: %#v", user)
|
|
}
|
|
if _, exists := user["phone_masked"]; exists {
|
|
t.Fatalf("platform response contains an unexpected phone mask: %#v", user)
|
|
}
|
|
if _, exists := user["address"]; exists {
|
|
t.Fatalf("platform exception exposed an address: %#v", user)
|
|
}
|
|
if user["contact_phone_masked"] != "139****9000" {
|
|
t.Fatalf("order contact phone was not kept masked: %#v", user)
|
|
}
|
|
|
|
account := map[string]any{"display_name": "平台主管", "phone": "13700137000"}
|
|
ProtectPreciseLocation(ctx, &models.PlatformAccount{}, account)
|
|
if account["display_name"] != "平台主管" || account["phone"] != "13700137000" {
|
|
t.Fatalf("platform account data was masked: %#v", account)
|
|
}
|
|
}
|
|
|
|
func TestNonPlatformResourceResponseStillMasksNamesAndPhone(t *testing.T) {
|
|
ctx, _ := gin.CreateTestContext(nil)
|
|
ctx.Set("Auth", &types.JwtClaims{Client: "user_app"})
|
|
value := map[string]any{"name": "张三", "real_name": "张三", "phone": "13800138000"}
|
|
|
|
ProtectPreciseLocation(ctx, &models.UserAccount{}, value)
|
|
if _, exists := value["name"]; exists {
|
|
t.Fatalf("non-platform response exposed a name: %#v", value)
|
|
}
|
|
if _, exists := value["real_name"]; exists {
|
|
t.Fatalf("non-platform response exposed a real name: %#v", value)
|
|
}
|
|
if _, exists := value["phone"]; exists {
|
|
t.Fatalf("non-platform response exposed a phone: %#v", value)
|
|
}
|
|
if value["name_masked"] != "张*" || value["phone_masked"] != "138****8000" {
|
|
t.Fatalf("non-platform response masks are incorrect: %#v", value)
|
|
}
|
|
}
|
|
|
|
func TestResourceRelationOptionalEmptyIdentityClearsRelation(t *testing.T) {
|
|
values, err := ResolveResourceRelations(
|
|
map[string]any{"warehouse_identity": ""},
|
|
nil,
|
|
[]ResourceRelation{{Input: "warehouse_identity", Column: "warehouse_id", Model: &models.ProductWarehouse{}}},
|
|
false,
|
|
)
|
|
if err != nil || values["warehouse_id"] != uint64(0) {
|
|
t.Fatalf("optional relation clear = (%#v, %v)", values, err)
|
|
}
|
|
}
|
|
|
|
func TestCommonMethodModesRemainHTTPCompatible(t *testing.T) {
|
|
if http.MethodGet == "" || http.MethodPost == "" {
|
|
t.Fatal("standard HTTP methods unavailable")
|
|
}
|
|
}
|
|
|
|
func TestGenericStatusRejectsDomainLifecycleValues(t *testing.T) {
|
|
for _, status := range []int{StatusDraft, StatusEnable, StatusDisable, StatusArchived, StatusFrozen} {
|
|
if !IsGenericRecordStatus(status) {
|
|
t.Fatalf("generic status %d was rejected", status)
|
|
}
|
|
}
|
|
if IsGenericRecordStatus(StatusCompleted) {
|
|
t.Fatal("business lifecycle status was accepted as generic entity status")
|
|
}
|
|
}
|
|
|
|
func TestCommerceAndFinanceRejectInvalidAmounts(t *testing.T) {
|
|
tests := []struct {
|
|
model any
|
|
values map[string]any
|
|
}{
|
|
{&models.EcProduct{}, map[string]any{"product_code": "p", "name": "P", "price_amount": -1.0}},
|
|
{&models.EcCart{}, map[string]any{"quantity": 0.0}},
|
|
{&models.EcOrder{}, map[string]any{"order_no": "o", "total_amount": -1.0}},
|
|
{&models.EcOrderItem{}, map[string]any{"product_snapshot": "{}", "quantity": -1.0, "sale_amount": 1.0}},
|
|
{&models.EcReview{}, map[string]any{"score": 6.0, "content": "bad"}},
|
|
{&models.FinPayment{}, map[string]any{"channel": "wallet", "amount": -1.0}},
|
|
}
|
|
for _, test := range tests {
|
|
if ValidateResourceValues(test.model, test.values, true) == nil {
|
|
t.Fatalf("%T accepted invalid values %#v", test.model, test.values)
|
|
}
|
|
}
|
|
}
|