Files
lti-api/internal/modules/daily-checklists/services/daily-checklist.service_test.go
2026-05-17 19:11:13 +07:00

433 lines
14 KiB
Go

package service
import (
"errors"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/glebarez/sqlite"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/repositories"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/validations"
"gorm.io/gorm"
)
func TestCreateOneRejectsWhenSameDateHasActiveEmptyKandang(t *testing.T) {
svc, db := setupDailyChecklistServiceTest(t)
insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-10"), dailyChecklistCategoryEmptyKandang, strPtr("DRAFT"), nil)
result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
Date: "2026-01-10",
KandangId: 1,
Category: "cleaning",
Status: "DRAFT",
})
if result != nil {
t.Fatalf("expected nil result, got %+v", result)
}
assertFiberErrorCode(t, serviceErr, fiber.StatusConflict)
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected HTTP status %d, got %d", fiber.StatusConflict, resp.StatusCode)
}
}
func TestCreateOneRejectsWhenSameDateHasRejectedEmptyKandang(t *testing.T) {
svc, db := setupDailyChecklistServiceTest(t)
insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-10"), dailyChecklistCategoryEmptyKandang, strPtr(dailyChecklistStatusRejected), nil)
result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
Date: "2026-01-10",
KandangId: 1,
Category: "cleaning",
Status: "DRAFT",
})
if result != nil {
t.Fatalf("expected nil result, got %+v", result)
}
assertFiberErrorCode(t, serviceErr, fiber.StatusConflict)
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected HTTP status %d, got %d", fiber.StatusConflict, resp.StatusCode)
}
}
func TestCreateOneAllowsWhenOnlySoftDeletedEmptyKandangExists(t *testing.T) {
svc, db := setupDailyChecklistServiceTest(t)
deletedAt := mustDateTime(t, "2026-01-11 10:00:00")
insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-10"), dailyChecklistCategoryEmptyKandang, strPtr("DRAFT"), &deletedAt)
result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
Date: "2026-01-10",
KandangId: 1,
Category: "cleaning",
Status: "DRAFT",
})
if serviceErr != nil {
t.Fatalf("expected no error, got %v", serviceErr)
}
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected HTTP status %d, got %d", fiber.StatusCreated, resp.StatusCode)
}
if result == nil {
t.Fatal("expected non-nil result")
}
if result.Category != "cleaning" {
t.Fatalf("expected category cleaning, got %s", result.Category)
}
var activeCount int64
if err := db.Model(&entity.DailyChecklist{}).
Where("kandang_id = ? AND date = ? AND category = ? AND deleted_at IS NULL", 1, mustDate(t, "2026-01-10"), "cleaning").
Count(&activeCount).Error; err != nil {
t.Fatalf("failed counting active checklists: %v", err)
}
if activeCount != 1 {
t.Fatalf("expected 1 active cleaning checklist, got %d", activeCount)
}
}
func TestCreateOneRejectsBulkEmptyKandangWhenDateRangeHasConflict(t *testing.T) {
svc, db := setupDailyChecklistServiceTest(t)
insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-03"), dailyChecklistCategoryEmptyKandang, strPtr("APPROVED"), nil)
result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
Date: "2026-01-01",
KandangId: 1,
Category: "cleaning",
Status: "DRAFT",
EmptyKandang: true,
EmptyKandangEndDate: "2026-01-05",
})
if result != nil {
t.Fatalf("expected nil result, got %+v", result)
}
assertFiberErrorCode(t, serviceErr, fiber.StatusConflict)
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected HTTP status %d, got %d", fiber.StatusConflict, resp.StatusCode)
}
var activeInRange int64
if err := db.Model(&entity.DailyChecklist{}).
Where("kandang_id = ? AND date BETWEEN ? AND ? AND deleted_at IS NULL", 1, mustDate(t, "2026-01-01"), mustDate(t, "2026-01-05")).
Count(&activeInRange).Error; err != nil {
t.Fatalf("failed counting checklists in range: %v", err)
}
if activeInRange != 1 {
t.Fatalf("expected only pre-existing row to remain in range, got %d rows", activeInRange)
}
}
func TestCreateOneRejectsBulkEmptyKandangWhenRangeHasNonEmptyChecklist(t *testing.T) {
svc, db := setupDailyChecklistServiceTest(t)
insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-03"), "cleaning", strPtr("APPROVED"), nil)
result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
Date: "2026-01-01",
KandangId: 1,
Category: "cleaning",
Status: "DRAFT",
EmptyKandang: true,
EmptyKandangEndDate: "2026-01-05",
})
if result != nil {
t.Fatalf("expected nil result, got %+v", result)
}
assertFiberErrorCode(t, serviceErr, fiber.StatusConflict)
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected HTTP status %d, got %d", fiber.StatusConflict, resp.StatusCode)
}
}
func TestCreateOneRejectsBulkEmptyKandangWhenRangeHasRejectedChecklist(t *testing.T) {
svc, db := setupDailyChecklistServiceTest(t)
insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-03"), "cleaning", strPtr(dailyChecklistStatusRejected), nil)
result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
Date: "2026-01-01",
KandangId: 1,
Category: "cleaning",
Status: "DRAFT",
EmptyKandang: true,
EmptyKandangEndDate: "2026-01-05",
})
if result != nil {
t.Fatalf("expected nil result, got %+v", result)
}
assertFiberErrorCode(t, serviceErr, fiber.StatusConflict)
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected HTTP status %d, got %d", fiber.StatusConflict, resp.StatusCode)
}
}
func TestCreateOneAllowsBulkEmptyKandangWhenRangeHasOnlySoftDeletedChecklist(t *testing.T) {
svc, db := setupDailyChecklistServiceTest(t)
deletedAt := mustDateTime(t, "2026-01-11 10:00:00")
insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-03"), "cleaning", strPtr("APPROVED"), &deletedAt)
result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
Date: "2026-01-01",
KandangId: 1,
Category: "cleaning",
Status: "DRAFT",
EmptyKandang: true,
EmptyKandangEndDate: "2026-01-05",
})
if serviceErr != nil {
t.Fatalf("expected no error, got %v", serviceErr)
}
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected HTTP status %d, got %d", fiber.StatusCreated, resp.StatusCode)
}
if result == nil {
t.Fatal("expected non-nil result")
}
if result.Category != dailyChecklistCategoryEmptyKandang {
t.Fatalf("expected category %s, got %s", dailyChecklistCategoryEmptyKandang, result.Category)
}
var activeInRange int64
if err := db.Model(&entity.DailyChecklist{}).
Where("kandang_id = ? AND date BETWEEN ? AND ? AND deleted_at IS NULL", 1, mustDate(t, "2026-01-01"), mustDate(t, "2026-01-05")).
Count(&activeInRange).Error; err != nil {
t.Fatalf("failed counting checklists in range: %v", err)
}
if activeInRange != 1 {
t.Fatalf("expected 1 active empty_kandang checklist created for range, got %d", activeInRange)
}
var emptyRangeCount int64
if err := db.Model(&entity.DailyChecklistEmptyKandang{}).
Where("kandang_id = ? AND start_date = ? AND end_date = ? AND deleted_at IS NULL", 1, mustDate(t, "2026-01-01"), mustDate(t, "2026-01-05")).
Count(&emptyRangeCount).Error; err != nil {
t.Fatalf("failed counting empty kandang ranges: %v", err)
}
if emptyRangeCount != 1 {
t.Fatalf("expected 1 empty kandang range record for [2026-01-01, 2026-01-05], got %d", emptyRangeCount)
}
}
func TestCreateOneReusesExistingChecklistWhenNoEmptyKandangConflict(t *testing.T) {
svc, db := setupDailyChecklistServiceTest(t)
existingID := insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-10"), "cleaning", strPtr("APPROVED"), nil)
result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
Date: "2026-01-10",
KandangId: 1,
Category: "cleaning",
Status: "DRAFT",
})
if serviceErr != nil {
t.Fatalf("expected no error, got %v", serviceErr)
}
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected HTTP status %d, got %d", fiber.StatusCreated, resp.StatusCode)
}
if result == nil {
t.Fatal("expected non-nil result")
}
if result.Id != existingID {
t.Fatalf("expected existing checklist id %d to be reused, got %d", existingID, result.Id)
}
var activeCount int64
if err := db.Model(&entity.DailyChecklist{}).
Where("kandang_id = ? AND date = ? AND category = ? AND deleted_at IS NULL", 1, mustDate(t, "2026-01-10"), "cleaning").
Count(&activeCount).Error; err != nil {
t.Fatalf("failed counting active checklists: %v", err)
}
if activeCount != 1 {
t.Fatalf("expected 1 active cleaning checklist, got %d", activeCount)
}
}
func setupDailyChecklistServiceTest(t *testing.T) (DailyChecklistService, *gorm.DB) {
t.Helper()
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=private"), &gorm.Config{})
if err != nil {
t.Fatalf("failed opening sqlite db: %v", err)
}
statements := []string{
`CREATE TABLE areas (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
created_by INTEGER NOT NULL,
created_at DATETIME NULL,
updated_at DATETIME NULL,
deleted_at DATETIME NULL
)`,
`CREATE TABLE locations (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
address TEXT NOT NULL,
area_id INTEGER NOT NULL,
created_by INTEGER NOT NULL,
created_at DATETIME NULL,
updated_at DATETIME NULL,
deleted_at DATETIME NULL
)`,
`CREATE TABLE kandang_groups (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
status TEXT NOT NULL,
location_id INTEGER NOT NULL,
pic_id INTEGER NOT NULL,
created_by INTEGER NOT NULL,
created_at DATETIME NULL,
updated_at DATETIME NULL,
deleted_at DATETIME NULL
)`,
`CREATE TABLE daily_checklists (
id INTEGER PRIMARY KEY AUTOINCREMENT,
kandang_id INTEGER NOT NULL,
checklist_id INTEGER NULL,
date DATE NOT NULL,
name TEXT NULL,
status TEXT NULL,
category TEXT NOT NULL,
total_score INTEGER NULL,
document_path TEXT NULL,
reject_reason TEXT NULL,
created_by INTEGER NULL,
deleted_by INTEGER NULL,
created_at DATETIME NULL,
updated_at DATETIME NULL,
deleted_at DATETIME NULL
)`,
`CREATE TABLE daily_checklist_empty_kandangs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
daily_checklist_id INTEGER NOT NULL,
kandang_id INTEGER NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
created_by INTEGER NULL,
deleted_by INTEGER NULL,
created_at DATETIME NULL,
updated_at DATETIME NULL,
deleted_at DATETIME NULL
)`,
`INSERT INTO areas (id, name, created_by, created_at, updated_at, deleted_at) VALUES (1, 'Area A', 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL)`,
`INSERT INTO locations (id, name, address, area_id, created_by, created_at, updated_at, deleted_at) VALUES (1, 'Farm A', 'Address', 1, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL)`,
`INSERT INTO kandang_groups (id, name, status, location_id, pic_id, created_by, created_at, updated_at, deleted_at) VALUES (1, 'Kandang A', 'ACTIVE', 1, 1, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL)`,
}
for _, stmt := range statements {
if err := db.Exec(stmt).Error; err != nil {
t.Fatalf("failed preparing schema: %v", err)
}
}
repo := repository.NewDailyChecklistRepository(db)
emptyRepo := repository.NewDailyChecklistEmptyKandangRepository(db)
svc := NewDailyChecklistService(repo, emptyRepo, nil, validator.New(), nil)
return svc, db
}
func runCreateOneRequest(t *testing.T, svc DailyChecklistService, req *validation.Create) (*entity.DailyChecklist, error, *http.Response) {
t.Helper()
app := fiber.New()
var (
result *entity.DailyChecklist
serviceErr error
)
app.Post("/", func(c *fiber.Ctx) error {
result, serviceErr = svc.CreateOne(c, req)
if serviceErr != nil {
return serviceErr
}
return c.SendStatus(fiber.StatusCreated)
})
resp, err := app.Test(httptest.NewRequest(http.MethodPost, "/", nil))
if err != nil {
t.Fatalf("failed running fiber request: %v", err)
}
return result, serviceErr, resp
}
func insertDailyChecklistRow(t *testing.T, db *gorm.DB, kandangID uint, date time.Time, category string, status *string, deletedAt *time.Time) uint {
t.Helper()
row := &entity.DailyChecklist{
KandangId: kandangID,
Date: date,
Category: category,
Status: status,
}
if deletedAt != nil {
row.DeletedAt = gorm.DeletedAt{
Time: *deletedAt,
Valid: true,
}
}
if err := db.Create(row).Error; err != nil {
t.Fatalf("failed inserting daily checklist row: %v", err)
}
return row.Id
}
func assertFiberErrorCode(t *testing.T, err error, expectedCode int) {
t.Helper()
if err == nil {
t.Fatal("expected error, got nil")
}
var fiberErr *fiber.Error
if !errors.As(err, &fiberErr) {
t.Fatalf("expected *fiber.Error, got %T (%v)", err, err)
}
if fiberErr.Code != expectedCode {
t.Fatalf("expected fiber error code %d, got %d", expectedCode, fiberErr.Code)
}
}
func mustDate(t *testing.T, raw string) time.Time {
t.Helper()
value, err := time.Parse(dailyChecklistDateLayout, raw)
if err != nil {
t.Fatalf("failed parsing date %q: %v", raw, err)
}
return value
}
func mustDateTime(t *testing.T, raw string) time.Time {
t.Helper()
value, err := time.Parse("2006-01-02 15:04:05", raw)
if err != nil {
t.Fatalf("failed parsing datetime %q: %v", raw, err)
}
return value
}
func strPtr(value string) *string {
v := value
return &v
}