Files
lti-api/test/integration/master_data/nonstock_test.go
T

310 lines
12 KiB
Go

package test
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"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 TestNonstockIntegration(t *testing.T) {
app, db := setupIntegrationApp(t)
uomID := createUom(t, app, "Unit Piece")
altUomID := createUom(t, app, "Unit Box")
supplierID1 := createSupplier(t, app, "Nonstock Supplier One", "ns1", string(utils.SupplierCategoryBOP))
supplierID2 := createSupplier(t, app, "Nonstock Supplier Two", "ns2", string(utils.SupplierCategoryBOP))
supplierID3 := createSupplier(t, app, "Nonstock Supplier Three", "ns3", string(utils.SupplierCategoryBOP))
sapronakSupplierID := createSupplier(t, app, "SAPRONAK Supplier", "sap1", string(utils.SupplierCategorySapronak))
nonstockFlags := []string{string(utils.FlagEkspedisi)}
t.Run("create nonstock without suppliers succeeds with empty relations", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/nonstocks", map[string]any{
"name": "Supplierless Nonstock",
"uom_id": uomID,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when suppliers omitted, got %d: %s", resp.StatusCode, string(body))
}
id := parseID(t, body)
ns := fetchNonstock(t, db, id)
if len(ns.Suppliers) != 0 {
t.Fatalf("expected no suppliers persisted, found %d", len(ns.Suppliers))
}
if len(ns.Flags) != 0 {
t.Fatalf("expected no flags persisted, found %d", len(ns.Flags))
}
resp, _ = doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/nonstocks/%d", id), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected cleanup delete to succeed, got %d", resp.StatusCode)
}
})
t.Run("create nonstock with unknown supplier fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/nonstocks", map[string]any{
"name": "Unknown Supplier Nonstock",
"uom_id": uomID,
"supplier_ids": []uint{99999},
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when supplier missing, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("create nonstock with sapronak supplier fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/nonstocks", map[string]any{
"name": "Invalid Category Nonstock",
"uom_id": uomID,
"supplier_ids": []uint{sapronakSupplierID},
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when supplier category is not BOP, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("create nonstock with invalid flags fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/nonstocks", map[string]any{
"name": "Invalid Flag Nonstock",
"uom_id": uomID,
"supplier_ids": []uint{supplierID1},
"flags": []string{"UNKNOWN"},
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when flags invalid, got %d: %s", resp.StatusCode, string(body))
}
})
var nonstockID uint
t.Run("create nonstock succeeds", func(t *testing.T) {
nonstockID = createNonstock(t, app, "Layer Feed", uomID, []uint{supplierID1, supplierID2, supplierID1}, nonstockFlags)
if nonstockID == 0 {
t.Fatal("expected nonstock id to be non zero")
}
ns := fetchNonstock(t, db, nonstockID)
if ns.Name != "Layer Feed" {
t.Fatalf("expected name Layer Feed, got %q", ns.Name)
}
if ns.UomId != uomID {
t.Fatalf("expected uom_id %d, got %d", uomID, ns.UomId)
}
if ns.CreatedBy != 1 {
t.Fatalf("expected created_by 1, got %d", ns.CreatedBy)
}
if len(ns.Suppliers) != 2 {
t.Fatalf("expected 2 unique suppliers, got %d", len(ns.Suppliers))
}
if len(ns.Flags) != len(nonstockFlags) {
t.Fatalf("expected %d flags, got %d", len(nonstockFlags), len(ns.Flags))
}
expectedFlags := make(map[string]struct{}, len(nonstockFlags))
for _, flag := range nonstockFlags {
expectedFlags[strings.ToUpper(flag)] = struct{}{}
}
for _, flag := range ns.Flags {
upper := strings.ToUpper(flag.Name)
if _, ok := expectedFlags[upper]; !ok {
t.Fatalf("unexpected flag stored: %s", upper)
}
delete(expectedFlags, upper)
}
if len(expectedFlags) != 0 {
t.Fatalf("missing flags after create: %v", expectedFlags)
}
})
t.Run("get nonstock detail includes suppliers", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when fetching nonstock, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Id uint `json:"id"`
Name string `json:"name"`
UomID uint `json:"uom_id"`
Suppliers []map[string]any `json:"suppliers"`
Flags []string `json:"flags"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse nonstock detail: %v", err)
}
if payload.Data.Id != nonstockID {
t.Fatalf("expected id %d, got %d", nonstockID, payload.Data.Id)
}
if payload.Data.UomID != uomID {
t.Fatalf("expected response uom_id %d, got %d", uomID, payload.Data.UomID)
}
if len(payload.Data.Suppliers) != 2 {
t.Fatalf("expected 2 suppliers in response, got %d", len(payload.Data.Suppliers))
}
if len(payload.Data.Flags) != len(nonstockFlags) {
t.Fatalf("expected %d flags in response, got %d", len(nonstockFlags), len(payload.Data.Flags))
}
expected := make(map[string]struct{}, len(nonstockFlags))
for _, flag := range nonstockFlags {
expected[strings.ToUpper(flag)] = struct{}{}
}
for _, flag := range payload.Data.Flags {
flag = strings.ToUpper(flag)
if _, ok := expected[flag]; !ok {
t.Fatalf("unexpected flag %s returned", flag)
}
delete(expected, flag)
}
if len(expected) != 0 {
t.Fatalf("missing flags in response: %v", expected)
}
})
t.Run("update nonstock with invalid uom fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), map[string]any{
"uom_id": 99999,
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when updating with invalid uom, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("update nonstock with invalid supplier fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), map[string]any{
"supplier_ids": []uint{supplierID1, 99999},
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when updating with invalid supplier, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("update nonstock with sapronak supplier fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), map[string]any{
"supplier_ids": []uint{sapronakSupplierID},
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when updating with non-BOP supplier, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("update nonstock with invalid flags fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), map[string]any{
"flags": []string{"BAD"},
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when updating flags invalid, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("update nonstock name uom and suppliers succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), map[string]any{
"name": "Layer Feed Premium",
"uom_id": altUomID,
"supplier_ids": []uint{supplierID3},
"flags": nonstockFlags,
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when updating nonstock, got %d: %s", resp.StatusCode, string(body))
}
ns := fetchNonstock(t, db, nonstockID)
if ns.Name != "Layer Feed Premium" {
t.Fatalf("expected name Layer Feed Premium, got %q", ns.Name)
}
if ns.UomId != altUomID {
t.Fatalf("expected uom_id %d, got %d", altUomID, ns.UomId)
}
if len(ns.Suppliers) != 1 || ns.Suppliers[0].Id != supplierID3 {
t.Fatalf("expected suppliers to contain only %d", supplierID3)
}
if len(ns.Flags) != len(nonstockFlags) {
t.Fatalf("expected flags retained, got %d", len(ns.Flags))
}
})
t.Run("clear suppliers succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), map[string]any{
"supplier_ids": []uint{},
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when clearing suppliers, got %d: %s", resp.StatusCode, string(body))
}
ns := fetchNonstock(t, db, nonstockID)
if len(ns.Suppliers) != 0 {
t.Fatalf("expected suppliers to be cleared, got %d entries", len(ns.Suppliers))
}
if len(ns.Flags) != len(nonstockFlags) {
t.Fatalf("expected flags unaffected, got %d", len(ns.Flags))
}
})
t.Run("clear nonstock flags succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), map[string]any{
"flags": []string{},
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when clearing nonstock flags, got %d: %s", resp.StatusCode, string(body))
}
ns := fetchNonstock(t, db, nonstockID)
if len(ns.Flags) != 0 {
t.Fatalf("expected flags cleared, got %d", len(ns.Flags))
}
})
t.Run("delete nonstock succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when deleting nonstock, got %d: %s", resp.StatusCode, string(body))
}
var ns entities.Nonstock
if err := db.First(&ns, nonstockID).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("expected nonstock to be deleted, got error %v", err)
}
var links int64
if err := db.Model(&entities.NonstockSupplier{}).Where("nonstock_id = ?", nonstockID).Count(&links).Error; err != nil {
t.Fatalf("failed counting nonstock suppliers: %v", err)
}
if links != 0 {
t.Fatalf("expected link table cleared, found %d rows", links)
}
var flagCount int64
if err := db.Model(&entities.Flag{}).
Where("flagable_id = ? AND flagable_type = ?", nonstockID, entities.FlagableTypeNonstock).
Count(&flagCount).Error; err != nil {
t.Fatalf("failed counting nonstock flags: %v", err)
}
if flagCount != 0 {
t.Fatalf("expected flags removed, found %d", flagCount)
}
})
t.Run("deleting nonstock twice returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when deleting missing nonstock, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("fetching deleted nonstock returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when fetching deleted nonstock, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("cleanup additional supplier references", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/suppliers/%d", supplierID3), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when deleting supplier, got %d: %s", resp.StatusCode, string(body))
}
doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/suppliers/%d", sapronakSupplierID), nil)
})
}