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.Flock{}, &entities.ProjectFlock{}, &entities.ProjectFlockKandang{}, &entities.Kandang{}, &entities.Warehouse{}, &entities.Uom{}, &entities.Customer{}, &entities.Supplier{}, &entities.Flag{}, &entities.ProductCategory{}, &entities.Nonstock{}, &entities.NonstockSupplier{}, &entities.Product{}, &entities.ProductSupplier{}, &entities.Fcr{}, &entities.FcrStandard{}, &entities.Bank{}, ); 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(fiber.Config{ErrorHandler: utils.ErrorHandler}) 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 createUom(t *testing.T, app *fiber.App, name string) uint { t.Helper() resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/uoms", map[string]any{"name": name}) if resp.StatusCode != fiber.StatusCreated { t.Fatalf("expected 201 when creating uom, 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, "status": "ACTIVE", "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 fetchKandang(t *testing.T, db *gorm.DB, id uint) entities.Kandang { t.Helper() var kandang entities.Kandang if err := db.Preload("ProjectFlock").First(&kandang, id).Error; err != nil { t.Fatalf("failed to fetch kandang: %v", err) } return kandang } func createSupplier(t *testing.T, app *fiber.App, name, alias, category string) uint { t.Helper() identifier := strings.ToLower(strings.ReplaceAll(name, " ", "_")) resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/suppliers", map[string]any{ "name": name, "alias": alias, "pic": "John Doe", "type": utils.CustomerSupplierTypeBisnis, "category": category, "hatchery": "Hatchery A", "phone": "081234567890", "email": fmt.Sprintf("%s@supplier.com", identifier), "address": "Supplier address", "npwp": "NPWP-123", "account_number": "ACC-SUPPLIER", "due_date": 30, }) if resp.StatusCode != fiber.StatusCreated { t.Fatalf("expected 201 when creating supplier, got %d: %s", resp.StatusCode, string(body)) } return parseID(t, body) } func createProductCategory(t *testing.T, app *fiber.App, name, code string) uint { t.Helper() resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/product-categories", map[string]any{ "name": name, "code": code, }) if resp.StatusCode != fiber.StatusCreated { t.Fatalf("expected 201 when creating product category, got %d: %s", resp.StatusCode, string(body)) } return parseID(t, body) } func fetchProductCategory(t *testing.T, db *gorm.DB, id uint) entities.ProductCategory { t.Helper() var pc entities.ProductCategory if err := db.Preload("CreatedUser").First(&pc, id).Error; err != nil { t.Fatalf("failed to fetch product category: %v", err) } return pc } func createProduct(t *testing.T, app *fiber.App, name, brand string, sku *string, uomID, categoryID uint, productPrice float64, supplierIDs []uint, flags []string) uint { t.Helper() payload := map[string]any{ "name": name, "brand": brand, "uom_id": uomID, "product_category_id": categoryID, "product_price": productPrice, "supplier_ids": supplierIDs, } if sku != nil { payload["sku"] = *sku } if len(flags) > 0 { payload["flags"] = flags } resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/products", payload) if resp.StatusCode != fiber.StatusCreated { t.Fatalf("expected 201 when creating product, got %d: %s", resp.StatusCode, string(body)) } return parseID(t, body) } func fetchProduct(t *testing.T, db *gorm.DB, id uint) entities.Product { t.Helper() var product entities.Product if err := db.Preload("CreatedUser"). Preload("Uom"). Preload("ProductCategory"). Preload("Suppliers", func(tx *gorm.DB) *gorm.DB { return tx.Order("suppliers.name ASC") }). Preload("Flags", func(tx *gorm.DB) *gorm.DB { return tx.Order("flags.name ASC") }). First(&product, id).Error; err != nil { t.Fatalf("failed to fetch product: %v", err) } return product } func fetchSupplier(t *testing.T, db *gorm.DB, id uint) entities.Supplier { t.Helper() var supplier entities.Supplier if err := db.Preload("CreatedUser").First(&supplier, id).Error; err != nil { t.Fatalf("failed to fetch supplier: %v", err) } return supplier } func createFcr(t *testing.T, app *fiber.App, name string, standards []map[string]any) uint { t.Helper() payload := map[string]any{ "name": name, "fcr_standards": standards, } resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/fcrs", payload) if resp.StatusCode != fiber.StatusCreated { t.Fatalf("expected 201 when creating fcr, got %d: %s", resp.StatusCode, string(body)) } return parseID(t, body) } func createFlock(t *testing.T, app *fiber.App, name string) uint { t.Helper() resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/flocks", map[string]any{ "name": name, }) if resp.StatusCode != fiber.StatusCreated { t.Fatalf("expected 201 when creating flock, got %d: %s", resp.StatusCode, string(body)) } return parseID(t, body) } func fetchFcr(t *testing.T, db *gorm.DB, id uint) entities.Fcr { t.Helper() var fcr entities.Fcr if err := db.Preload("CreatedUser"). Preload("Standards", func(tx *gorm.DB) *gorm.DB { return tx.Order("weight ASC") }). First(&fcr, id).Error; err != nil { t.Fatalf("failed to fetch fcr: %v", err) } return fcr } func createNonstock(t *testing.T, app *fiber.App, name string, uomID uint, supplierIDs []uint, flags []string) uint { t.Helper() payload := map[string]any{ "name": name, "uom_id": uomID, "supplier_ids": supplierIDs, } if len(flags) > 0 { payload["flags"] = flags } resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/nonstocks", payload) if resp.StatusCode != fiber.StatusCreated { t.Fatalf("expected 201 when creating nonstock, got %d: %s", resp.StatusCode, string(body)) } return parseID(t, body) } func fetchNonstock(t *testing.T, db *gorm.DB, id uint) entities.Nonstock { t.Helper() var nonstock entities.Nonstock if err := db.Preload("CreatedUser"). Preload("Uom"). Preload("Suppliers", func(tx *gorm.DB) *gorm.DB { return tx.Order("suppliers.name ASC") }). Preload("Flags", func(tx *gorm.DB) *gorm.DB { return tx.Order("flags.name ASC") }). First(&nonstock, id).Error; err != nil { t.Fatalf("failed to fetch nonstock: %v", err) } return nonstock } 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 } func createBank(t *testing.T, app *fiber.App, name, alias, accountNumber string, owner any) uint { t.Helper() payload := map[string]any{ "name": name, "alias": alias, "account_number": accountNumber, "owner": owner, } resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/banks", payload) if resp.StatusCode != fiber.StatusCreated { t.Fatalf("expected 201 when creating bank, got %d: %s", resp.StatusCode, string(body)) } return parseID(t, body) } func fetchBank(t *testing.T, db *gorm.DB, id uint) entities.Bank { t.Helper() var bank entities.Bank if err := db.Preload("CreatedUser").First(&bank, id).Error; err != nil { t.Fatalf("failed to fetch bank: %v", err) } return bank }