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) } }