mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 05:21:57 +00:00
adjust validation create daily checklist empty kandang
This commit is contained in:
@@ -127,6 +127,8 @@ const (
|
||||
dailyChecklistCategoryEmptyKandang = "empty_kandang"
|
||||
dailyChecklistStatusRejected = "REJECTED"
|
||||
dailyChecklistStatusDraft = "DRAFT"
|
||||
dailyChecklistErrEmptyKandangExist = "DailyChecklist cannot be created because empty_kandang already exists for at least one date in range"
|
||||
dailyChecklistErrDateOverlapExist = "DailyChecklist cannot be created because at least one date in range already has a checklist"
|
||||
)
|
||||
|
||||
func NewDailyChecklistService(repo repository.DailyChecklistRepository, phaseRepo phaseRepo.PhasesRepository, validate *validator.Validate, documentSvc commonSvc.DocumentService) DailyChecklistService {
|
||||
@@ -538,10 +540,21 @@ func (s *dailyChecklistService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
||||
targetID := uint(0)
|
||||
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
||||
if err := s.lockKandangForChecklistCreation(tx, req.KandangId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if req.EmptyKandang {
|
||||
if err := s.validateNoChecklistOverlapForEmptyKandang(tx, req.KandangId, date, endDate); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.createBulkDailyChecklists(tx, req.KandangId, date, endDate, category, status, &targetID)
|
||||
}
|
||||
|
||||
if err := s.validateNoEmptyKandangConflict(tx, req.KandangId, date, endDate); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.createOrReuseSingleDailyChecklist(tx, req.KandangId, date, category, status, &targetID)
|
||||
})
|
||||
if err != nil {
|
||||
@@ -552,6 +565,56 @@ func (s *dailyChecklistService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
||||
return s.GetOne(c, targetID)
|
||||
}
|
||||
|
||||
func (s *dailyChecklistService) lockKandangForChecklistCreation(tx *gorm.DB, kandangID uint) error {
|
||||
if kandangID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid kandang id")
|
||||
}
|
||||
|
||||
var lockedKandangID uint
|
||||
query := tx.Table("kandang_groups").Select("id").Where("id = ?", kandangID)
|
||||
if tx.Dialector.Name() != "sqlite" {
|
||||
query = query.Clauses(clause.Locking{Strength: "UPDATE"})
|
||||
}
|
||||
|
||||
if err := query.Take(&lockedKandangID).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "Kandang not found")
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dailyChecklistService) validateNoChecklistOverlapForEmptyKandang(tx *gorm.DB, kandangID uint, startDate, endDate time.Time) error {
|
||||
var conflictCount int64
|
||||
if err := tx.Model(&entity.DailyChecklist{}).
|
||||
Where("kandang_id = ? AND date BETWEEN ? AND ? AND deleted_at IS NULL", kandangID, startDate, endDate).
|
||||
Count(&conflictCount).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if conflictCount > 0 {
|
||||
return fiber.NewError(fiber.StatusConflict, dailyChecklistErrDateOverlapExist)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dailyChecklistService) validateNoEmptyKandangConflict(tx *gorm.DB, kandangID uint, startDate, endDate time.Time) error {
|
||||
var conflictCount int64
|
||||
if err := tx.Model(&entity.DailyChecklist{}).
|
||||
Where("kandang_id = ? AND date BETWEEN ? AND ? AND category = ? AND deleted_at IS NULL", kandangID, startDate, endDate, dailyChecklistCategoryEmptyKandang).
|
||||
Count(&conflictCount).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if conflictCount > 0 {
|
||||
return fiber.NewError(fiber.StatusConflict, dailyChecklistErrEmptyKandangExist)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dailyChecklistService) createOrReuseSingleDailyChecklist(tx *gorm.DB, kandangID uint, date time.Time, category, status string, targetID *uint) error {
|
||||
existing := new(entity.DailyChecklist)
|
||||
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
|
||||
@@ -0,0 +1,409 @@
|
||||
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 != 5 {
|
||||
t.Fatalf("expected 5 active checklists created for range, got %d", activeInRange)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)`,
|
||||
`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)
|
||||
svc := NewDailyChecklistService(repo, 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
|
||||
}
|
||||
Reference in New Issue
Block a user