Files
zsp-project/backend/test/zsp_contract_handler_test.go
2026-06-03 20:59:39 +08:00

539 lines
17 KiB
Go

package test
import (
"bytes"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/rovina/zsp-backend/internal/dto"
"github.com/rovina/zsp-backend/internal/handler"
"github.com/rovina/zsp-backend/internal/model"
"github.com/rovina/zsp-backend/internal/service"
"mime/multipart"
)
// mockZSPContractService implements service.ZSPContractService for testing
type mockZSPContractService struct {
createFolderFn func(req *dto.ZSPFolderCreateRequest) error
getFoldersFn func() ([]model.ZSPContractFolder, error)
getFolderByIdFn func(folderID string) (*model.ZSPContractFolder, error)
deleteFolderByIdFn func(folderID string) error
updateFolderByIdFn func(folderID string, req *dto.ZSPFolderUpdateRequest) error
uploadContractFn func(req *dto.ZSPContractCreateRequest, fileHeader *multipart.FileHeader) error
getContractByIdFn func(contractID string) (*model.ZSPContractFile, error)
getContractWithDetailsFn func(contractID string) (*model.ZSPContractFile, error)
listContractsFn func(folderID string) ([]model.ZSPContractFile, error)
listContractsWithDetailsFn func(folderID string) ([]model.ZSPContractFile, error)
deleteContractByIdFn func(contractID string) error
updateContractByIdFn func(contractID string, req *dto.ZSPContractUpdateRequest) error
createContractDetailFn func(req *dto.ZSPContractDetailCreateRequest) error
updateContractDetailFn func(req *dto.ZSPContractDetailUpdateRequest) error
getContractDetailByIdFn func(detailID string) (*model.ZSPContractDetail, error)
listContractDetailsFn func(contractFileID string) ([]model.ZSPContractDetail, error)
deleteContractDetailByIdFn func(detailID string) error
batchCreateContractDetailsFn func(req *dto.ZSPContractDetailBatchCreateRequest) error
}
func (m *mockZSPContractService) CreateFolder(req *dto.ZSPFolderCreateRequest) error {
return m.createFolderFn(req)
}
func (m *mockZSPContractService) GetFolders() ([]model.ZSPContractFolder, error) {
return m.getFoldersFn()
}
func (m *mockZSPContractService) GetFolderById(folderID string) (*model.ZSPContractFolder, error) {
return m.getFolderByIdFn(folderID)
}
func (m *mockZSPContractService) DeleteFolderById(folderID string) error {
return m.deleteFolderByIdFn(folderID)
}
func (m *mockZSPContractService) UpdateFolderById(folderID string, req *dto.ZSPFolderUpdateRequest) error {
return m.updateFolderByIdFn(folderID, req)
}
func (m *mockZSPContractService) UploadContract(req *dto.ZSPContractCreateRequest, fileHeader *multipart.FileHeader) error {
return m.uploadContractFn(req, fileHeader)
}
func (m *mockZSPContractService) GetContractById(contractID string) (*model.ZSPContractFile, error) {
return m.getContractByIdFn(contractID)
}
func (m *mockZSPContractService) GetContractWithDetails(contractID string) (*model.ZSPContractFile, error) {
return m.getContractWithDetailsFn(contractID)
}
func (m *mockZSPContractService) ListContracts(folderID string) ([]model.ZSPContractFile, error) {
return m.listContractsFn(folderID)
}
func (m *mockZSPContractService) ListContractsWithDetails(folderID string) ([]model.ZSPContractFile, error) {
return m.listContractsWithDetailsFn(folderID)
}
func (m *mockZSPContractService) DeleteContractById(contractID string) error {
return m.deleteContractByIdFn(contractID)
}
func (m *mockZSPContractService) UpdateContract(contract *model.ZSPContractFile) error {
return nil
}
func (m *mockZSPContractService) UpdateContractById(contractID string, req *dto.ZSPContractUpdateRequest) error {
return m.updateContractByIdFn(contractID, req)
}
func (m *mockZSPContractService) CreateContractDetail(req *dto.ZSPContractDetailCreateRequest) error {
return m.createContractDetailFn(req)
}
func (m *mockZSPContractService) UpdateContractDetail(req *dto.ZSPContractDetailUpdateRequest) error {
return m.updateContractDetailFn(req)
}
func (m *mockZSPContractService) GetContractDetailById(detailID string) (*model.ZSPContractDetail, error) {
return m.getContractDetailByIdFn(detailID)
}
func (m *mockZSPContractService) ListContractDetails(contractFileID string) ([]model.ZSPContractDetail, error) {
return m.listContractDetailsFn(contractFileID)
}
func (m *mockZSPContractService) DeleteContractDetailById(detailID string) error {
return m.deleteContractDetailByIdFn(detailID)
}
func (m *mockZSPContractService) BatchCreateContractDetails(req *dto.ZSPContractDetailBatchCreateRequest) error {
return m.batchCreateContractDetailsFn(req)
}
var _ service.ZSPContractService = (*mockZSPContractService)(nil)
func setupZSPContractRouter(h *handler.ZSPContractHandler) *gin.Engine {
gin.SetMode(gin.TestMode)
r := gin.New()
api := r.Group("/api")
{
zsp := api.Group("/zsp-contract")
{
zsp.POST("/folders", h.CreateFolder)
zsp.GET("/folders", h.GetFolders)
zsp.GET("/folders/:id", h.GetFolder)
zsp.DELETE("/folders/:id", h.DeleteFolder)
zsp.PUT("/folders/:id", h.UpdateFolder)
zsp.POST("/contracts", h.UploadContract)
zsp.GET("/contracts/:id", h.GetContractWithDetails)
zsp.GET("/contracts", h.ListContractsWithDetails)
zsp.PUT("/contracts/:id", h.UpdateContract)
zsp.DELETE("/contracts/:id", h.DeleteContract)
zsp.POST("/contracts/:id/details", h.CreateContractDetail)
zsp.GET("/contracts/:id/details", h.ListContractDetails)
zsp.POST("/details/batch", h.BatchCreateContractDetails)
zsp.GET("/details/:id", h.GetContractDetail)
zsp.PUT("/details/:id", h.UpdateContractDetail)
zsp.DELETE("/details/:id", h.DeleteContractDetail)
}
}
return r
}
// ========== Folder Tests ==========
func TestZSPContractCreateFolder_Success(t *testing.T) {
mock := &mockZSPContractService{
createFolderFn: func(req *dto.ZSPFolderCreateRequest) error {
return nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
body, _ := json.Marshal(dto.ZSPFolderCreateRequest{Name: "test folder", Description: "test desc"})
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/zsp-contract/folders", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
if resp["success"] != true {
t.Errorf("expected success true, got %v", resp["success"])
}
}
func TestZSPContractCreateFolder_BadRequest(t *testing.T) {
mock := &mockZSPContractService{
createFolderFn: func(req *dto.ZSPFolderCreateRequest) error {
return nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/zsp-contract/folders", bytes.NewReader([]byte("{}")))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("expected status 400 for missing required field, got %d", w.Code)
}
}
func TestZSPContractGetFolders_Success(t *testing.T) {
mock := &mockZSPContractService{
getFoldersFn: func() ([]model.ZSPContractFolder, error) {
return []model.ZSPContractFolder{
{ID: 1, Name: "folder1"},
{ID: 2, Name: "folder2"},
}, nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/zsp-contract/folders", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
if resp["success"] != true {
t.Errorf("expected success true, got %v", resp["success"])
}
}
func TestZSPContractGetFolders_Error(t *testing.T) {
mock := &mockZSPContractService{
getFoldersFn: func() ([]model.ZSPContractFolder, error) {
return nil, errors.New("database error")
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/zsp-contract/folders", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusInternalServerError {
t.Errorf("expected status 500, got %d", w.Code)
}
}
func TestZSPContractGetFolder_Success(t *testing.T) {
mock := &mockZSPContractService{
getFolderByIdFn: func(folderID string) (*model.ZSPContractFolder, error) {
return &model.ZSPContractFolder{ID: 1, Name: "test folder"}, nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/zsp-contract/folders/1", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
}
func TestZSPContractDeleteFolder_Success(t *testing.T) {
mock := &mockZSPContractService{
deleteFolderByIdFn: func(folderID string) error {
return nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/api/zsp-contract/folders/1", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
}
func TestZSPContractDeleteFolder_Error(t *testing.T) {
mock := &mockZSPContractService{
deleteFolderByIdFn: func(folderID string) error {
return errors.New("folder not found")
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/api/zsp-contract/folders/999", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusInternalServerError {
t.Errorf("expected status 500, got %d", w.Code)
}
}
func TestZSPContractUpdateFolder_Success(t *testing.T) {
mock := &mockZSPContractService{
updateFolderByIdFn: func(folderID string, req *dto.ZSPFolderUpdateRequest) error {
return nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
body, _ := json.Marshal(dto.ZSPFolderUpdateRequest{Name: "updated name", Description: "updated desc"})
w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", "/api/zsp-contract/folders/1", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
}
// ========== Contract Tests ==========
func TestZSPContractListContracts_Success(t *testing.T) {
mock := &mockZSPContractService{
listContractsWithDetailsFn: func(folderID string) ([]model.ZSPContractFile, error) {
return []model.ZSPContractFile{
{ID: 1, ContractNumber: "C001", CompanyName: "公司A"},
{ID: 2, ContractNumber: "C002", CompanyName: "公司B"},
}, nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/zsp-contract/contracts?folderId=1", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
if resp["success"] != true {
t.Errorf("expected success true, got %v", resp["success"])
}
}
func TestZSPContractGetContract_Success(t *testing.T) {
mock := &mockZSPContractService{
getContractWithDetailsFn: func(contractID string) (*model.ZSPContractFile, error) {
return &model.ZSPContractFile{ID: 1, ContractNumber: "C001", CompanyName: "公司A"}, nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/zsp-contract/contracts/1", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
}
func TestZSPContractGetContract_Error(t *testing.T) {
mock := &mockZSPContractService{
getContractWithDetailsFn: func(contractID string) (*model.ZSPContractFile, error) {
return nil, errors.New("contract not found")
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/zsp-contract/contracts/999", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusInternalServerError {
t.Errorf("expected status 500, got %d", w.Code)
}
}
func TestZSPContractDeleteContract_Success(t *testing.T) {
mock := &mockZSPContractService{
deleteContractByIdFn: func(contractID string) error {
return nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/api/zsp-contract/contracts/1", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
}
func TestZSPContractUpdateContract_Success(t *testing.T) {
mock := &mockZSPContractService{
updateContractByIdFn: func(contractID string, req *dto.ZSPContractUpdateRequest) error {
return nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
body, _ := json.Marshal(dto.ZSPContractUpdateRequest{ContractNumber: "C001-Updated"})
w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", "/api/zsp-contract/contracts/1", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
}
// ========== Contract Detail Tests ==========
func TestZSPContractCreateContractDetail_Success(t *testing.T) {
mock := &mockZSPContractService{
createContractDetailFn: func(req *dto.ZSPContractDetailCreateRequest) error {
return nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
body, _ := json.Marshal(dto.ZSPContractDetailCreateRequest{
ContractFileID: 1,
CommodityName: "燕麦",
TotalQuantity: 100,
UnitPrice: 500.0,
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/zsp-contract/contracts/1/details", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
}
func TestZSPContractListContractDetails_Success(t *testing.T) {
mock := &mockZSPContractService{
listContractDetailsFn: func(contractFileID string) ([]model.ZSPContractDetail, error) {
return []model.ZSPContractDetail{
{ID: 1, CommodityName: "燕麦", TotalQuantity: 100},
{ID: 2, CommodityName: "大豆", TotalQuantity: 200},
}, nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/zsp-contract/contracts/1/details", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
if resp["success"] != true {
t.Errorf("expected success true, got %v", resp["success"])
}
}
func TestZSPContractGetContractDetail_Success(t *testing.T) {
mock := &mockZSPContractService{
getContractDetailByIdFn: func(detailID string) (*model.ZSPContractDetail, error) {
return &model.ZSPContractDetail{ID: 1, CommodityName: "燕麦", TotalQuantity: 100}, nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/zsp-contract/details/1", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
}
func TestZSPContractDeleteContractDetail_Success(t *testing.T) {
mock := &mockZSPContractService{
deleteContractDetailByIdFn: func(detailID string) error {
return nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/api/zsp-contract/details/1", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
}
func TestZSPContractUpdateContractDetail_Success(t *testing.T) {
mock := &mockZSPContractService{
updateContractDetailFn: func(req *dto.ZSPContractDetailUpdateRequest) error {
return nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
body, _ := json.Marshal(dto.ZSPContractDetailUpdateRequest{
ID: 1,
CommodityName: "燕麦-更新",
TotalQuantity: 150,
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", "/api/zsp-contract/details/1", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
}
func TestZSPContractBatchCreateContractDetails_Success(t *testing.T) {
mock := &mockZSPContractService{
batchCreateContractDetailsFn: func(req *dto.ZSPContractDetailBatchCreateRequest) error {
return nil
},
}
h := handler.NewZSPContractHandler(mock)
router := setupZSPContractRouter(h)
details := []dto.ZSPContractDetailCreateRequest{
{ContractFileID: 1, CommodityName: "燕麦", TotalQuantity: 100, UnitPrice: 500.0},
{ContractFileID: 1, CommodityName: "大豆", TotalQuantity: 200, UnitPrice: 400.0},
}
body, _ := json.Marshal(dto.ZSPContractDetailBatchCreateRequest{
ContractFileID: 1,
Details: details,
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/zsp-contract/details/batch", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
}