refactor(platform): remove safety management module
This commit is contained in:
@@ -15,7 +15,6 @@ func TestDashboardOverviewReturnsZeroValuesForAnEmptyDatabase(t *testing.T) {
|
||||
`SELECT count\(\*\) FROM "delivery_basic" WHERE status = \$1`,
|
||||
`SELECT count\(\*\) FROM "staff_account" WHERE work_status = \$1`,
|
||||
`SELECT count\(\*\) FROM "user_account" WHERE status = \$1`,
|
||||
`SELECT count\(\*\) FROM "safe_event" WHERE status = \$1`,
|
||||
} {
|
||||
mock.ExpectQuery(regexp.MustCompile(query).String()).WithArgs(sqlmock.AnyArg()).WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
|
||||
}
|
||||
|
||||
@@ -75,7 +75,6 @@ func ExpectedResources() []ResourceContract {
|
||||
resourceContract("staff", "staff_account", Writable, "list"), resourceContract("staff", "staff_credential", Writable, "list"),
|
||||
resourceContract("user", "user_account", Writable, "list"), resourceContract("user", "user_address", Writable, "list"), resourceContract("user", "user_service_relation", Writable, "list"),
|
||||
resourceContract("device", "dev_smart_cylinder_valve", Writable, "list"), resourceContract("device", "dev_device_binding", Writable, "list"), resourceContract("device", "dev_telemetry", ReadOnly, "list"),
|
||||
resourceContract("safety", "safe_rule", Writable, "list"), resourceContract("safety", "safe_event", Writable, "list"), resourceContract("safety", "safe_inspection", Writable, "list"), resourceContract("safety", "safe_event_disposal", AppendOnly, "list"),
|
||||
resourceContract("ec", "ec_category", Writable, "list"), resourceContract("ec", "ec_product", Writable, "list"), resourceContract("ec", "ec_product_attribute", Writable, "list"), resourceContract("ec", "ec_product_image", Writable, "list"), resourceContract("ec", "ec_cart", Writable, "list"), resourceContract("ec", "ec_order", Writable, "list"), resourceContract("ec", "ec_order_item", Writable, "list"), resourceContract("ec", "ec_review", Writable, "list"),
|
||||
resourceContract("delivery", "delivery_task", Writable, "list"), resourceContract("delivery", "delivery_track", Writable, "list"), resourceContract("delivery", "delivery_track_point", ReadOnly, "list"),
|
||||
resourceContract("finance", "fin_payment", Writable, "list"), resourceContract("finance", "fin_settlement", Writable, "list"), resourceContract("finance", "fin_reconciliation", Writable, "list"),
|
||||
@@ -101,8 +100,6 @@ func resourcePath(domain, name string) string {
|
||||
return "/user/address"
|
||||
case "user_service_relation":
|
||||
return "/user/service_relation"
|
||||
case "safe_event_disposal":
|
||||
return "/safety/safe_event/:identity/disposals"
|
||||
default:
|
||||
return "/" + domain + "/" + name
|
||||
}
|
||||
|
||||
@@ -42,16 +42,13 @@ func TestContentUsesCMSResourceAndRemovedDomainsAreAbsent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafeResourceContracts(t *testing.T) {
|
||||
legacySafetyPrefix := "sa" + "f_"
|
||||
|
||||
assertContract(t, ExpectedResources(), "safety", "safe_rule", Writable, "list")
|
||||
assertContract(t, ExpectedResources(), "safety", "safe_event", Writable, "list")
|
||||
assertContract(t, ExpectedResources(), "safety", "safe_inspection", Writable, "list")
|
||||
assertContract(t, ExpectedResources(), "safety", "safe_event_disposal", AppendOnly, "list")
|
||||
func TestSafetyResourcesAreAbsent(t *testing.T) {
|
||||
removed := map[string]struct{}{
|
||||
"safe_rule": {}, "safe_event": {}, "safe_inspection": {}, "safe_event_disposal": {},
|
||||
}
|
||||
for _, contract := range ExpectedResources() {
|
||||
if contract.Domain == "safety" && strings.HasPrefix(contract.Name, legacySafetyPrefix) {
|
||||
t.Fatalf("legacy resource contract %s/%s must not be registered", contract.Domain, contract.Name)
|
||||
if _, exists := removed[contract.Name]; exists || contract.Domain == "safety" {
|
||||
t.Fatalf("removed safety resource %s/%s remains registered", contract.Domain, contract.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,17 +255,6 @@ func TestPrepareResourceValuesResolvesRequiredIdentityRelationsAndRejectsInvalid
|
||||
}
|
||||
|
||||
func TestPrepareResourceValuesAcceptsArbitraryTextFields(t *testing.T) {
|
||||
t.Run("safety rule", func(t *testing.T) {
|
||||
ctx, _ := updateContext(http.MethodPost, "/safety/safe_rule", "", []byte(`{"rule_code":"pressure-limit","threshold":"{invalid}","action":"close-valve","gray_scope":"north region"}`))
|
||||
values, err := prepareResourceValues(ctx, &models.SafeRule{}, []string{"rule_code", "threshold", "action", "gray_scope"}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if values["threshold"] != `{invalid}` || values["gray_scope"] != `north region` {
|
||||
t.Fatalf("text values were not preserved: %#v", values)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("order item", func(t *testing.T) {
|
||||
_, mock := setupPlatformRoleDatabase(t)
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT "id" FROM "ec_order" WHERE identity = $1 ORDER BY "ec_order"."id" LIMIT $2`)).
|
||||
@@ -292,12 +278,6 @@ func TestPrepareResourceValuesAcceptsArbitraryTextFields(t *testing.T) {
|
||||
assertMockExpectations(t, mock)
|
||||
})
|
||||
|
||||
t.Run("arbitrary text", func(t *testing.T) {
|
||||
ctx, _ := updateContext(http.MethodPost, "/safety/safe_rule", "", []byte(`{"rule_code":"pressure-limit","threshold":"{invalid}","action":"close-valve"}`))
|
||||
if _, err := prepareResourceValues(ctx, &models.SafeRule{}, []string{"rule_code", "threshold", "action"}, nil); err != nil {
|
||||
t.Fatalf("arbitrary text was rejected: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateGasAccountResolvesGasBasicIdentityBeforePersisting(t *testing.T) {
|
||||
@@ -350,7 +330,6 @@ func TestKeywordColumnsUseSafeTextAllowlist(t *testing.T) {
|
||||
model any
|
||||
want []string
|
||||
}{
|
||||
{"safety rule excludes arbitrary text fields", &models.SafeRule{}, []string{"rule_code", "action"}},
|
||||
{"gas basic excludes sensitive fields", &models.GasBasic{}, []string{"code", "name"}},
|
||||
{"user address has no searchable safe text", &models.UserAddress{}, []string{}},
|
||||
}
|
||||
@@ -655,26 +634,6 @@ func TestNonRootCannotAssignPlatformAccountRole(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListSafetyEventDisposalsReturnsOnlyTheRequestedEventHistory(t *testing.T) {
|
||||
_, mock := setupPlatformRoleDatabase(t)
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "safe_event_disposal" WHERE safe_event_identity = $1`)).
|
||||
WithArgs("event-a").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "safe_event_disposal" WHERE safe_event_identity = $1 ORDER BY created_at asc LIMIT $2`)).
|
||||
WithArgs("event-a", 20).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "created_at", "updated_at", "status", "version", "safe_event_identity", "action", "reason", "operator_identity"}).
|
||||
AddRow(uint64(9), "disposal-a", nil, nil, "enabled", 1, "event-a", "close", "resolved", "operator-a"))
|
||||
|
||||
ctx, recorder := updateContext(http.MethodGet, "/safety/safe_event/event-a/disposals", "event-a", nil)
|
||||
ListSafetyEventDisposals(ctx)
|
||||
|
||||
assertResponseCode(t, recorder, 0)
|
||||
if !strings.Contains(recorder.Body.String(), `"safe_event_identity":"event-a"`) || strings.Contains(recorder.Body.String(), `"id":`) {
|
||||
t.Fatalf("disposal history did not keep the event identity-only shape: %s", recorder.Body.String())
|
||||
}
|
||||
assertMockExpectations(t, mock)
|
||||
}
|
||||
|
||||
func TestListGasAccountProjectsGasBasicIdentityAndNeverReturnsRelationID(t *testing.T) {
|
||||
_, mock := setupPlatformRoleDatabase(t)
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "gas_account"`)).
|
||||
@@ -820,33 +779,6 @@ func TestGetDeliveryTrackPointReturnsCoordinatesWithPreciseLocationScope(t *test
|
||||
assertMockExpectations(t, mock)
|
||||
}
|
||||
|
||||
func TestDisposeSafetyEventUpdatesEventAndAppendsOperatorActionTransactionally(t *testing.T) {
|
||||
_, mock := setupPlatformRoleDatabase(t)
|
||||
now := time.Now().UTC()
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "safe_event" WHERE identity = $1 ORDER BY "safe_event"."id" LIMIT $2`)).
|
||||
WithArgs("event-a", 1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "created_at", "updated_at", "status", "version", "event_code", "level", "title", "smart_cylinder_valve_identity", "sla_at"}).
|
||||
AddRow(uint64(3), "event-a", now, now, "open", 1, "E-1", 2, "alarm", "valve-a", nil))
|
||||
mock.ExpectExec(regexp.QuoteMeta(`UPDATE "safe_event" SET "status"=$1,"updated_at"=$2 WHERE identity = $3`)).
|
||||
WithArgs("disposed", sqlmock.AnyArg(), "event-a").
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "safe_event_disposal" ("identity","created_at","updated_at","status","version","safe_event_identity","action","reason","operator_identity") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING "id"`)).
|
||||
WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "enabled", 1, "event-a", "close", "resolved", "operator-a").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uint64(1)))
|
||||
mock.ExpectCommit()
|
||||
|
||||
ctx, recorder := updateContext(http.MethodPost, "/safety/safe_event/event-a/disposals", "event-a", []byte(`{"action":"close","reason":"resolved"}`))
|
||||
ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"})
|
||||
DisposeSafetyEvent(ctx)
|
||||
|
||||
assertResponseCode(t, recorder, 0)
|
||||
if !strings.Contains(recorder.Body.String(), `"operator_identity":"operator-a"`) {
|
||||
t.Fatalf("disposal omitted its operator: %s", recorder.Body.String())
|
||||
}
|
||||
assertMockExpectations(t, mock)
|
||||
}
|
||||
|
||||
func TestReplacePlatformRoleMenusAllowsAnEmptySetToClearAssignmentsTransactionally(t *testing.T) {
|
||||
_, mock := setupPlatformRoleDatabase(t)
|
||||
mock.ExpectBegin()
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ResourceRelation accepts a stable external identity while retaining the
|
||||
@@ -530,73 +529,6 @@ func stripInternalIDs(value any) any {
|
||||
return value
|
||||
}
|
||||
|
||||
// DisposeSafetyEvent atomically updates an event and appends its operator-owned
|
||||
// action record. Disposal records deliberately have no update or delete route.
|
||||
func ListSafetyEventDisposals(ctx *gin.Context) {
|
||||
page, size := pageSize(ctx)
|
||||
var list []models.SafeEventDisposal
|
||||
query := impl.DBService.Model(&models.SafeEventDisposal{}).Where("safe_event_identity = ?", ctx.Param("identity"))
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
if err := query.Order("created_at asc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"total": total, "list": resourceResponse(list)})
|
||||
}
|
||||
|
||||
func DisposeSafetyEvent(ctx *gin.Context) {
|
||||
claims, err := middleware.ParseAuth(ctx)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
var request struct {
|
||||
Action string `json:"action" binding:"required,max=64"`
|
||||
Reason string `json:"reason" binding:"max=2000"`
|
||||
Status string `json:"status" binding:"max=32"`
|
||||
}
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
if request.Status == "" {
|
||||
request.Status = "disposed"
|
||||
}
|
||||
var disposal models.SafeEventDisposal
|
||||
err = impl.DBService.Transaction(func(transaction *gorm.DB) error {
|
||||
var event models.SafeEvent
|
||||
if err := transaction.Where("identity = ?", ctx.Param("identity")).First(&event).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if result := transaction.Model(&models.SafeEvent{}).Where("identity = ?", event.Identity).Update("status", request.Status); result.Error != nil {
|
||||
return result.Error
|
||||
} else if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
disposal = models.SafeEventDisposal{
|
||||
Entity: newEntity("enabled"),
|
||||
SafeEventIdentity: event.Identity,
|
||||
Action: request.Action,
|
||||
Reason: request.Reason,
|
||||
OperatorIdentity: claims.Identity,
|
||||
}
|
||||
return transaction.Create(&disposal).Error
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
|
||||
return
|
||||
}
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, resourceResponse(disposal))
|
||||
}
|
||||
|
||||
// GetEcOrder returns the order together with its immutable item snapshots.
|
||||
func GetEcOrder(ctx *gin.Context) {
|
||||
var order models.EcOrder
|
||||
|
||||
Reference in New Issue
Block a user