material_manage_system/internal/handler/application_organization_test.go

124 lines
5.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handler
import (
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/wmms/wmms/internal/model"
)
// 本文件验收 Task 5C 的申请/审批接口隔离:列表、详情、取消、驳回、审批都带请求头公司,
// 在 A 上下文操作 B 的申请读取阶段就命中空行,回 404,且不落任何状态改动。
// 中间件链:AuthMiddleware → OrganizationMiddleware → Handler,所有 SQL 期望都必须带对组织参数。
const (
appListCountPattern = `SELECT COUNT\(\*\) FROM material_applications a WHERE a.organization_id = \? AND a.user_id = \?`
appListPattern = `SELECT a.id, a.organization_id, a.user_id`
appUpdatePattern = `UPDATE material_applications SET status`
)
func applicationHeadRows(t *testing.T, organizationID, id, userID int64, status string) *sqlmock.Rows {
t.Helper()
now := time.Now()
return sqlmock.NewRows([]string{"id", "organization_id", "user_id", "purpose", "status", "rejection_reason", "approved_by", "created_at", "updated_at", "username"}).
AddRow(id, organizationID, userID, "领用", status, "", 0, now, now, "shared")
}
func emptyApplicationHeadRows() *sqlmock.Rows {
return sqlmock.NewRows([]string{"id", "organization_id", "user_id", "purpose", "status", "rejection_reason", "approved_by", "created_at", "updated_at", "username"})
}
func emptyApplicationItemRows() *sqlmock.Rows {
return sqlmock.NewRows([]string{"id", "application_id", "material_id", "quantity", "material_code", "name"})
}
// TestApplicationListScopesToOrganization:我的申请列表按请求公司 + 当前用户查询,
// 计数与数据查询都带组织参数。
func TestApplicationListScopesToOrganization(t *testing.T) {
mock := contextMock(t)
expectContextPrincipal(mock, "warehouse_admin")
expectMaterialOrgAccess(mock, 1)
mock.ExpectQuery(appListCountPattern).WithArgs(int64(1), int64(7)).WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
mock.ExpectQuery(appListPattern).WithArgs(int64(1), int64(7), 20, 0).WillReturnRows(emptyApplicationHeadRows())
w := newStockRequest(t, "GET", "/api/applications/my", "", 1)
if w.Code != 200 {
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
// TestApplicationCancelScopesToOrganization:本人取消待审申请时,条件更新带请求公司、
// 申请 ID 和原状态 pending。
func TestApplicationCancelScopesToOrganization(t *testing.T) {
mock := contextMock(t)
expectContextPrincipal(mock, "warehouse_admin")
expectMaterialOrgAccess(mock, 1)
mock.ExpectQuery(appListPattern).WithArgs(int64(9), int64(1)).WillReturnRows(applicationHeadRows(t, 1, 9, 7, model.ApplicationStatusPending))
mock.ExpectQuery("SELECT ai.id, ai.application_id").WithArgs(int64(1), int64(9)).WillReturnRows(emptyApplicationItemRows())
mock.ExpectExec(appUpdatePattern).WithArgs(model.ApplicationStatusCancelled, int64(1), int64(9), model.ApplicationStatusPending).WillReturnResult(sqlmock.NewResult(0, 1))
w := newStockRequest(t, "POST", "/api/applications/9/cancel", "", 1)
if w.Code != 200 {
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
// TestApplicationCancelCrossOrganizationReturnsNotFound:A 上下文取消 B 的申请,读取阶段
// 命中空行,回 404,不执行任何条件更新。
func TestApplicationCancelCrossOrganizationReturnsNotFound(t *testing.T) {
mock := contextMock(t)
expectContextPrincipal(mock, "warehouse_admin")
expectMaterialOrgAccess(mock, 1)
mock.ExpectQuery(appListPattern).WithArgs(int64(9), int64(1)).WillReturnRows(emptyApplicationHeadRows())
w := newStockRequest(t, "POST", "/api/applications/9/cancel", "", 1)
if w.Code != 404 {
t.Fatalf("status=%d body=%s, want 404", w.Code, w.Body.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
// TestApplicationRejectCrossOrganizationReturnsNotFound:审批人驳回别家公司的申请回 404。
func TestApplicationRejectCrossOrganizationReturnsNotFound(t *testing.T) {
mock := contextMock(t)
expectContextPrincipal(mock, "super_admin")
expectMaterialOrgAccess(mock, 1)
mock.ExpectQuery(appListPattern).WithArgs(int64(9), int64(1)).WillReturnRows(emptyApplicationHeadRows())
w := newStockRequest(t, "POST", "/api/applications/9/reject", `{"reason":"no"}`, 1)
if w.Code != 404 {
t.Fatalf("status=%d body=%s, want 404", w.Code, w.Body.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
// TestApplicationApproveCrossOrganizationReturnsNotFound:自动出库审批在事务内锁定别家
// 公司的申请命中空行,回 404,整体回滚,不解析本公司 scope。
func TestApplicationApproveCrossOrganizationReturnsNotFound(t *testing.T) {
mock := contextMock(t)
expectContextPrincipal(mock, "super_admin")
expectMaterialOrgAccess(mock, 1)
mock.ExpectBegin()
mock.ExpectQuery(appListPattern).WithArgs(int64(9), int64(1)).WillReturnRows(emptyApplicationHeadRows())
mock.ExpectRollback()
w := newStockRequest(t, "POST", "/api/applications/9/approve", `{"auto_outbound":true}`, 1)
if w.Code != 404 {
t.Fatalf("status=%d body=%s, want 404", w.Code, w.Body.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}