124 lines
5.2 KiB
Go
124 lines
5.2 KiB
Go
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)
|
||
}
|
||
}
|