Feat(BE-36,37,38,39): master area, customer, kandang, location, warehouse

This commit is contained in:
Hafizh A. Y
2025-10-02 10:51:15 +07:00
parent dbc1f79a36
commit e8905be856
79 changed files with 3745 additions and 169 deletions
+27
View File
@@ -0,0 +1,27 @@
package test
import (
"net/http"
"testing"
"github.com/gofiber/fiber/v2"
)
func TestAreaIntegration(t *testing.T) {
app, db := setupIntegrationApp(t)
t.Run("create area trims name", func(t *testing.T) {
areaID := createArea(t, app, " Area Trim ")
if got := fetchAreaName(t, db, areaID); got != "Area Trim" {
t.Fatalf("expected trimmed name, got %q", got)
}
})
t.Run("duplicate area returns conflict", func(t *testing.T) {
createArea(t, app, "Duplicate Area")
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/areas", map[string]any{"name": "Duplicate Area"})
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected 409 conflict, got %d: %s", resp.StatusCode, string(body))
}
})
}
@@ -0,0 +1,189 @@
package test
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"testing"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
)
func TestCustomerIntegration(t *testing.T) {
app, db := setupIntegrationApp(t)
t.Run("creating customer without existing pic fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
"name": "Invalid Customer",
"pic_id": 9999,
"type": utils.CustomerSupplierTypeBisnis,
"address": "Somewhere",
"phone": "0800000000",
"email": "invalid@example.com",
"account_number": "ACC-INVALID",
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when pic is missing, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("creating customer with invalid type fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
"name": "Invalid Type",
"pic_id": 1,
"type": "UNKNOWN",
"address": "Somewhere",
"phone": "081234567891",
"email": "invalid-type@example.com",
"account_number": "ACC-INVALID-TYPE",
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when type is invalid, got %d: %s", resp.StatusCode, string(body))
}
})
const customerName = "Customer Alpha"
var customerID uint
t.Run("creating customer succeeds", func(t *testing.T) {
customerID = createCustomer(t, app, customerName, 1)
customer := fetchCustomer(t, db, customerID)
if customer.Name != customerName {
t.Fatalf("expected name %q, got %q", customerName, customer.Name)
}
if customer.PicId != 1 {
t.Fatalf("expected pic id 1, got %d", customer.PicId)
}
if customer.Type != string(utils.CustomerSupplierTypeBisnis) {
t.Fatalf("expected type %q, got %q", utils.CustomerSupplierTypeBisnis, customer.Type)
}
})
t.Run("creating customer with duplicate name fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
"name": customerName,
"pic_id": 1,
"type": utils.CustomerSupplierTypeBisnis,
"address": "Duplicate address",
"phone": "0811111111",
"email": "duplicate@example.com",
"account_number": "ACC-DUP",
})
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected 409 when creating duplicate customer, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("getting existing customer succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when fetching customer, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Id uint `json:"id"`
Name string `json:"name"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse customer response: %v", err)
}
if payload.Data.Id != customerID {
t.Fatalf("expected id %d, got %d", customerID, payload.Data.Id)
}
if payload.Data.Name != customerName {
t.Fatalf("expected name %q, got %q", customerName, payload.Data.Name)
}
})
const updatedName = "Customer Gamma"
t.Run("updating customer name succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/customers/%d", customerID), map[string]any{
"name": updatedName,
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when updating customer, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Name string `json:"name"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse update response: %v", err)
}
if payload.Data.Name != updatedName {
t.Fatalf("expected updated name %q, got %q", updatedName, payload.Data.Name)
}
customer := fetchCustomer(t, db, customerID)
if customer.Name != updatedName {
t.Fatalf("expected persisted name %q, got %q", updatedName, customer.Name)
}
})
t.Run("updating customer type succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/customers/%d", customerID), map[string]any{
"type": utils.CustomerSupplierTypeIndividual,
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when updating customer type, got %d: %s", resp.StatusCode, string(body))
}
customer := fetchCustomer(t, db, customerID)
if customer.Type != string(utils.CustomerSupplierTypeIndividual) {
t.Fatalf("expected persisted type %q, got %q", utils.CustomerSupplierTypeIndividual, customer.Type)
}
})
t.Run("updating customer with invalid type fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/customers/%d", customerID), map[string]any{
"type": "random-type",
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when updating with invalid type, got %d: %s", resp.StatusCode, string(body))
}
customer := fetchCustomer(t, db, customerID)
if customer.Type != string(utils.CustomerSupplierTypeIndividual) {
t.Fatalf("expected type to remain %q after invalid update, got %q", utils.CustomerSupplierTypeIndividual, customer.Type)
}
})
t.Run("updating non existent customer returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, "/api/master-data/customers/9999", map[string]any{
"name": "Does Not Matter",
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when updating missing customer, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("deleting customer succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when deleting customer, got %d: %s", resp.StatusCode, string(body))
}
var customer entities.Customer
if err := db.First(&customer, customerID).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("expected customer to be deleted, got error %v", err)
}
})
t.Run("deleting non existent customer returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when deleting missing customer, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("getting deleted customer returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when fetching deleted customer, got %d: %s", resp.StatusCode, string(body))
}
})
}
@@ -0,0 +1,36 @@
package test
import (
"net/http"
"testing"
"github.com/gofiber/fiber/v2"
)
func TestKandangIntegration(t *testing.T) {
app, _ := setupIntegrationApp(t)
areaID := createArea(t, app, "Area Kandang")
locationID := createLocation(t, app, "Location For Kandang", "Address", areaID)
t.Run("create kandang success", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
"name": "Kandang OK",
"location_id": locationID,
"pic_id": 1,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("create kandang with unknown location fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
"name": "Kandang Fail",
"location_id": 999,
"pic_id": 1,
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404, got %d: %s", resp.StatusCode, string(body))
}
})
}
@@ -0,0 +1,35 @@
package test
import (
"net/http"
"testing"
"github.com/gofiber/fiber/v2"
)
func TestLocationIntegration(t *testing.T) {
app, _ := setupIntegrationApp(t)
t.Run("creating location without existing area fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/locations", map[string]any{
"name": "Loc A",
"address": "Address",
"area_id": 999, // non-existent
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("creating location succeeds", func(t *testing.T) {
areaID := createArea(t, app, "Area For Location")
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/locations", map[string]any{
"name": "Location OK",
"address": "Addr",
"area_id": areaID,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
}
})
}
+188
View File
@@ -0,0 +1,188 @@
package test
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
"time"
"github.com/glebarez/sqlite"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/route"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
)
func setupIntegrationApp(t *testing.T) (*fiber.App, *gorm.DB) {
t.Helper()
dir := t.TempDir()
dsn := filepath.Join(dir, "integration.db")
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
if err != nil {
t.Fatalf("failed to open sqlite database: %v", err)
}
if err := db.Exec("PRAGMA foreign_keys = ON").Error; err != nil {
t.Fatalf("failed to enable foreign keys: %v", err)
}
if err := db.AutoMigrate(
&entities.User{},
&entities.Area{},
&entities.Location{},
&entities.Kandang{},
&entities.Warehouse{},
&entities.Uom{},
&entities.Customer{},
); err != nil {
t.Fatalf("auto migrate failed: %v", err)
}
seedUser := entities.User{
Id: 1,
IdUser: 1001,
Email: "tester@example.com",
Name: "Tester",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if err := db.Create(&seedUser).Error; err != nil {
t.Fatalf("failed to seed user: %v", err)
}
app := fiber.New()
route.Routes(app, db)
return app, db
}
func doJSONRequest(t *testing.T, app *fiber.App, method, path string, payload any) (*http.Response, []byte) {
t.Helper()
var body io.Reader
if payload != nil {
buf := &bytes.Buffer{}
if err := json.NewEncoder(buf).Encode(payload); err != nil {
t.Fatalf("failed to encode payload: %v", err)
}
body = buf
}
req := httptest.NewRequest(method, path, body)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
resp, err := app.Test(req, -1)
if err != nil {
t.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
return resp, data
}
func parseID(t *testing.T, body []byte) uint {
t.Helper()
var resp struct {
Data struct {
Id uint `json:"id"`
} `json:"data"`
}
if err := json.Unmarshal(body, &resp); err != nil {
t.Fatalf("failed to parse response: %v", err)
}
return resp.Data.Id
}
func createArea(t *testing.T, app *fiber.App, name string) uint {
t.Helper()
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/areas", map[string]any{"name": name})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating area, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func createLocation(t *testing.T, app *fiber.App, name, address string, areaID uint) uint {
t.Helper()
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/locations", map[string]any{
"name": name,
"address": address,
"area_id": areaID,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating location, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func createKandang(t *testing.T, app *fiber.App, name string, locationID, picID uint) uint {
t.Helper()
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
"name": name,
"location_id": locationID,
"pic_id": picID,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating kandang, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func createCustomer(t *testing.T, app *fiber.App, name string, picID uint) uint {
t.Helper()
identifier := strings.ToLower(strings.ReplaceAll(name, " ", "_"))
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
"name": name,
"pic_id": picID,
"type": utils.CustomerSupplierTypeBisnis,
"address": "Customer address",
"phone": "081234567890",
"email": fmt.Sprintf("%s@example.com", identifier),
"account_number": fmt.Sprintf("ACC-%s", identifier),
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating customer, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func fetchCustomer(t *testing.T, db *gorm.DB, id uint) entities.Customer {
t.Helper()
var customer entities.Customer
if err := db.Preload("Pic").Preload("CreatedUser").First(&customer, id).Error; err != nil {
t.Fatalf("failed to fetch customer: %v", err)
}
return customer
}
func fetchAreaName(t *testing.T, db *gorm.DB, id uint) string {
t.Helper()
var area entities.Area
if err := db.First(&area, id).Error; err != nil {
t.Fatalf("failed to fetch area: %v", err)
}
return area.Name
}
func fetchWarehouse(t *testing.T, db *gorm.DB, id uint) entities.Warehouse {
t.Helper()
var wh entities.Warehouse
if err := db.First(&wh, id).Error; err != nil {
t.Fatalf("failed to fetch warehouse: %v", err)
}
return wh
}
@@ -0,0 +1,96 @@
package test
import (
"net/http"
"testing"
"github.com/gofiber/fiber/v2"
)
func TestWarehouseIntegration(t *testing.T) {
app, db := setupIntegrationApp(t)
areaID := createArea(t, app, "Warehouse Area")
locationID := createLocation(t, app, "Location WH", "Addr", areaID)
kandangID := createKandang(t, app, "Kandang WH", locationID, 1)
t.Run("type AREA only needs area_id", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
"name": "WH Area",
"type": "AREA",
"area_id": areaID,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("type AREA rejects location_id", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
"name": "WH Area Invalid",
"type": "AREA",
"area_id": areaID,
"location_id": locationID,
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("type LOKASI requires location_id", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
"name": "WH Lokasi Fail",
"type": "LOKASI",
"area_id": areaID,
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("type LOKASI success", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
"name": "WH Lokasi",
"type": "LOKASI",
"area_id": areaID,
"location_id": locationID,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
}
whID := parseID(t, body)
wh := fetchWarehouse(t, db, whID)
if wh.LocationId == nil || *wh.LocationId != locationID {
t.Fatalf("expected location_id %d, got %v", locationID, wh.LocationId)
}
})
t.Run("type KANDANG requires all ids", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
"name": "WH Kandang Fail",
"type": "KANDANG",
"area_id": areaID,
"location_id": locationID,
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("type KANDANG success", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
"name": "WH Kandang",
"type": "KANDANG",
"area_id": areaID,
"location_id": locationID,
"kandang_id": kandangID,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
}
whID := parseID(t, body)
wh := fetchWarehouse(t, db, whID)
if wh.KandangId == nil || *wh.KandangId != kandangID {
t.Fatalf("expected kandang_id %d, got %v", kandangID, wh.KandangId)
}
})
}