init push
This commit is contained in:
931
internal/transport/http/handlers/dul_handler_test.go
Normal file
931
internal/transport/http/handlers/dul_handler_test.go
Normal file
@@ -0,0 +1,931 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
"wucher/internal/domain/base"
|
||||
"wucher/internal/domain/contact"
|
||||
"wucher/internal/domain/dul"
|
||||
filemanager "wucher/internal/domain/file_manager"
|
||||
filemanagerrealtime "wucher/internal/realtime/filemanager"
|
||||
"wucher/internal/shared/pkg/uuidv7"
|
||||
)
|
||||
|
||||
type dulServiceMock struct {
|
||||
createFn func(context.Context, *dul.DUL) error
|
||||
createErr error
|
||||
updateErr error
|
||||
deleteErr error
|
||||
getErr error
|
||||
listErr error
|
||||
getResult *dul.DUL
|
||||
listRows []dul.DUL
|
||||
listTotal int64
|
||||
lastListID [][]byte
|
||||
}
|
||||
|
||||
func (m *dulServiceMock) Create(ctx context.Context, row *dul.DUL) error {
|
||||
if m.createFn != nil {
|
||||
return m.createFn(ctx, row)
|
||||
}
|
||||
if m.createErr != nil {
|
||||
return m.createErr
|
||||
}
|
||||
if len(row.ID) == 0 {
|
||||
row.ID = uuidv7.MustBytes()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *dulServiceMock) Update(_ context.Context, _ *dul.DUL) error { return m.updateErr }
|
||||
func (m *dulServiceMock) Delete(_ context.Context, _ []byte, _ []byte) error {
|
||||
return m.deleteErr
|
||||
}
|
||||
func (m *dulServiceMock) GetByID(_ context.Context, _ []byte) (*dul.DUL, error) {
|
||||
if m.getErr != nil {
|
||||
return nil, m.getErr
|
||||
}
|
||||
return m.getResult, nil
|
||||
}
|
||||
func (m *dulServiceMock) List(_ context.Context, _ string, _ string, _ int, _ int, baseIDs [][]byte) ([]dul.DUL, int64, error) {
|
||||
m.lastListID = baseIDs
|
||||
if m.listErr != nil {
|
||||
return nil, 0, m.listErr
|
||||
}
|
||||
return m.listRows, m.listTotal, nil
|
||||
}
|
||||
|
||||
type baseServiceMock struct {
|
||||
listRows []base.Base
|
||||
listErr error
|
||||
}
|
||||
|
||||
func (m *baseServiceMock) CreateBase(_ context.Context, _ *base.Base, _ string) error { return nil }
|
||||
func (m *baseServiceMock) UpdateBase(_ context.Context, _ *base.Base, _ string) error { return nil }
|
||||
func (m *baseServiceMock) DeleteBase(_ context.Context, _ []byte, _ string) (*base.Base, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *baseServiceMock) GetBaseByID(_ context.Context, _ []byte, _ string) (*base.Base, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *baseServiceMock) ListBases(_ context.Context, _, _ string, _, _ int, _ string, _ *bool) ([]base.Base, int64, error) {
|
||||
if m.listErr != nil {
|
||||
return nil, 0, m.listErr
|
||||
}
|
||||
return m.listRows, int64(len(m.listRows)), nil
|
||||
}
|
||||
|
||||
type contactServiceMock struct {
|
||||
isAdmin bool
|
||||
adminErr error
|
||||
item *contact.ContactListItem
|
||||
getErr error
|
||||
}
|
||||
|
||||
func (m *contactServiceMock) ListContacts(_ context.Context, _ contact.ListFilter) ([]contact.ContactListItem, int64, error) {
|
||||
return nil, 0, nil
|
||||
}
|
||||
func (m *contactServiceMock) GetContactByID(_ context.Context, _ []byte) (*contact.ContactListItem, error) {
|
||||
return m.item, m.getErr
|
||||
}
|
||||
func (m *contactServiceMock) IsActorAdmin(_ context.Context) (bool, error) {
|
||||
return m.isAdmin, m.adminErr
|
||||
}
|
||||
func (m *contactServiceMock) ResolveRoleCodeByID(_ context.Context, _ []byte) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
func (m *contactServiceMock) CreateContact(_ context.Context, _ contact.CreateInput) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *contactServiceMock) UpdateContact(_ context.Context, _ contact.UpdateInput) error {
|
||||
return nil
|
||||
}
|
||||
func (m *contactServiceMock) DeleteContact(_ context.Context, _ []byte) error { return nil }
|
||||
func (m *contactServiceMock) UpdateContactStatus(_ context.Context, _ []byte, _ bool) error {
|
||||
return nil
|
||||
}
|
||||
func (m *contactServiceMock) UpdateContactRole(_ context.Context, _ []byte, _ string) error {
|
||||
return nil
|
||||
}
|
||||
func (m *contactServiceMock) UpdatePilotCategory(_ context.Context, _ []byte, _ string) error {
|
||||
return nil
|
||||
}
|
||||
func (m *contactServiceMock) UpdateLicense(_ context.Context, _ []byte, _ *string) error {
|
||||
return nil
|
||||
}
|
||||
func (m *contactServiceMock) UpdateChiefFlags(_ context.Context, _ []byte, _ contact.ProfilePatch) error {
|
||||
return nil
|
||||
}
|
||||
func (m *contactServiceMock) UpdateResponsibleComplaints(_ context.Context, _ []byte, _ bool) error {
|
||||
return nil
|
||||
}
|
||||
func (m *contactServiceMock) BulkUpdateStatus(_ context.Context, _ [][]byte, _ bool) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (m *contactServiceMock) CheckSensitiveAction(_ context.Context, _ []byte, _, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newDULApp(h *DULHandler) *fiber.App {
|
||||
app := fiber.New()
|
||||
app.Post("/api/v1/dul/create", func(c *fiber.Ctx) error {
|
||||
c.Locals("user_id", uuidv7.MustBytes())
|
||||
return h.Create(c)
|
||||
})
|
||||
app.Patch("/api/v1/dul/update/:id", func(c *fiber.Ctx) error {
|
||||
c.Locals("user_id", uuidv7.MustBytes())
|
||||
return h.Update(c)
|
||||
})
|
||||
app.Delete("/api/v1/dul/delete/:id", func(c *fiber.Ctx) error {
|
||||
c.Locals("user_id", uuidv7.MustBytes())
|
||||
return h.Delete(c)
|
||||
})
|
||||
app.Get("/api/v1/dul/get/:id", h.Get)
|
||||
app.Get("/api/v1/dul/get-all", func(c *fiber.Ctx) error {
|
||||
c.Locals("user_id", uuidv7.MustBytes())
|
||||
return h.List(c)
|
||||
})
|
||||
app.Get("/api/v1/dul/get-all/dt", func(c *fiber.Ctx) error {
|
||||
c.Locals("user_id", uuidv7.MustBytes())
|
||||
return h.ListDatatable(c)
|
||||
})
|
||||
return app
|
||||
}
|
||||
|
||||
func newDULEventsApp(h *FileManagerEventsHandler, actor []byte) *fiber.App {
|
||||
app := fiber.New()
|
||||
app.Use(func(c *fiber.Ctx) error {
|
||||
if actor != nil {
|
||||
c.Locals("user_id", append([]byte(nil), actor...))
|
||||
}
|
||||
return c.Next()
|
||||
})
|
||||
app.Get("/api/v1/dul/events", h.Stream)
|
||||
return app
|
||||
}
|
||||
|
||||
func doReqDUL(t *testing.T, app *fiber.App, method, path string, body []byte) (*http.Response, map[string]any) {
|
||||
t.Helper()
|
||||
req, err := http.NewRequest(method, path, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
t.Fatalf("new request: %v", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("app test: %v", err)
|
||||
}
|
||||
raw, _ := io.ReadAll(resp.Body)
|
||||
out := map[string]any{}
|
||||
_ = json.Unmarshal(raw, &out)
|
||||
return resp, out
|
||||
}
|
||||
|
||||
func TestDULHandler_CRUDAndList(t *testing.T) {
|
||||
baseID := uuidv7.MustBytes()
|
||||
baseIDStr, _ := uuidv7.BytesToString(baseID)
|
||||
dulID := uuidv7.MustBytes()
|
||||
dulIDStr, _ := uuidv7.BytesToString(dulID)
|
||||
|
||||
svc := &dulServiceMock{
|
||||
getResult: &dul.DUL{
|
||||
ID: dulID,
|
||||
BaseID: baseID,
|
||||
Name: "DUL A",
|
||||
Date: time.Date(2026, 5, 25, 0, 0, 0, 0, time.UTC),
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
No: 1,
|
||||
},
|
||||
listRows: []dul.DUL{{
|
||||
ID: dulID,
|
||||
BaseID: baseID,
|
||||
Name: "DUL A",
|
||||
Date: time.Date(2026, 5, 25, 0, 0, 0, 0, time.UTC),
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
No: 1,
|
||||
}},
|
||||
listTotal: 1,
|
||||
}
|
||||
bsvc := &baseServiceMock{
|
||||
listRows: []base.Base{{
|
||||
ID: baseID,
|
||||
BaseName: "Halim",
|
||||
DUL: true,
|
||||
BaseCategory: base.BaseCategory{
|
||||
Name: "hems",
|
||||
},
|
||||
}},
|
||||
}
|
||||
h := NewDULHandler(svc).WithBaseService(bsvc)
|
||||
app := newDULApp(h)
|
||||
|
||||
createBody := []byte(`{"data":{"type":"dul_create","attributes":{"name":"DUL A","date":"2026-05-25","base_id":"` + baseIDStr + `"}}}`)
|
||||
resp, _ := doReqDUL(t, app, http.MethodPost, "/api/v1/dul/create", createBody)
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("expected 201, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
updateBody := []byte(`{"data":{"type":"dul_update","attributes":{"name":"DUL B"}}}`)
|
||||
resp, _ = doReqDUL(t, app, http.MethodPatch, "/api/v1/dul/update/"+dulIDStr, updateBody)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
resp, _ = doReqDUL(t, app, http.MethodGet, "/api/v1/dul/get/"+dulIDStr, nil)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200 get, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
resp, body := doReqDUL(t, app, http.MethodGet, "/api/v1/dul/get-all?page=1&size=20", nil)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200 list, got %d", resp.StatusCode)
|
||||
}
|
||||
meta, _ := body["meta"].(map[string]any)
|
||||
if meta == nil || meta["total_bases"] == nil {
|
||||
t.Fatalf("expected meta total_bases")
|
||||
}
|
||||
|
||||
resp, body = doReqDUL(t, app, http.MethodGet, "/api/v1/dul/get-all/dt?page=1&size=20&draw=1", nil)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200 dt, got %d", resp.StatusCode)
|
||||
}
|
||||
meta, _ = body["meta"].(map[string]any)
|
||||
if meta == nil || meta["records_total"] == nil || meta["page_number"] == nil {
|
||||
t.Fatalf("expected datatable meta fields")
|
||||
}
|
||||
|
||||
resp, _ = doReqDUL(t, app, http.MethodDelete, "/api/v1/dul/delete/"+dulIDStr, nil)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200 delete, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDULHandler_CreateUpdate_FileUUIDFlow(t *testing.T) {
|
||||
baseID := uuidv7.MustBytes()
|
||||
baseIDStr, _ := uuidv7.BytesToString(baseID)
|
||||
dulID := uuidv7.MustBytes()
|
||||
dulIDStr, _ := uuidv7.BytesToString(dulID)
|
||||
fileID1 := uuidv7.MustBytes()
|
||||
fileID1Str, _ := uuidv7.BytesToString(fileID1)
|
||||
fileID2 := uuidv7.MustBytes()
|
||||
fileID2Str, _ := uuidv7.BytesToString(fileID2)
|
||||
|
||||
svc := &dulServiceMock{
|
||||
getResult: &dul.DUL{
|
||||
ID: dulID,
|
||||
BaseID: baseID,
|
||||
Name: "DUL A",
|
||||
Date: time.Date(2026, 5, 26, 0, 0, 0, 0, time.UTC),
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
No: 1,
|
||||
},
|
||||
}
|
||||
|
||||
createCalls := make([]filemanager.CreateAttachmentParams, 0, 2)
|
||||
mockFM := &fileManagerAttachmentHandlerServiceMock{
|
||||
getAttachmentFn: func(_ context.Context, _ []byte) (*filemanager.Attachment, error) {
|
||||
return nil, nil
|
||||
},
|
||||
getFileFn: func(_ context.Context, id []byte) (*filemanager.File, error) {
|
||||
return &filemanager.File{ID: append([]byte(nil), id...), Status: filemanager.FileStatusReady}, nil
|
||||
},
|
||||
listAttachmentsFn: func(context.Context, filemanager.ListAttachmentsParams) ([]filemanager.Attachment, int64, error) {
|
||||
return nil, 0, nil
|
||||
},
|
||||
createAttachmentFn: func(_ context.Context, p filemanager.CreateAttachmentParams) (*filemanager.Attachment, error) {
|
||||
createCalls = append(createCalls, p)
|
||||
return &filemanager.Attachment{ID: uuidv7.MustBytes(), FileID: append([]byte(nil), p.FileID...)}, nil
|
||||
},
|
||||
}
|
||||
|
||||
h := NewDULHandler(svc).WithFileManagerService(mockFM)
|
||||
app := newDULApp(h)
|
||||
|
||||
t.Run("create accepts file_uuid", func(t *testing.T) {
|
||||
body := []byte(`{"data":{"type":"dul_create","attributes":{"name":"DUL A","date":"2026-05-26","base_id":"` + baseIDStr + `","file_uuid":["` + fileID1Str + `"]}}}`)
|
||||
resp, _ := doReqDUL(t, app, http.MethodPost, "/api/v1/dul/create", body)
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("expected 201, got %d", resp.StatusCode)
|
||||
}
|
||||
if len(createCalls) != 1 {
|
||||
t.Fatalf("expected 1 create attachment call, got %d", len(createCalls))
|
||||
}
|
||||
if !bytes.Equal(createCalls[0].FileID, fileID1) {
|
||||
t.Fatalf("expected first attachment file id = file_uuid[0]")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("update accepts file_uuid", func(t *testing.T) {
|
||||
body := []byte(`{"data":{"type":"dul_update","attributes":{"file_uuid":["` + fileID2Str + `"]}}}`)
|
||||
resp, _ := doReqDUL(t, app, http.MethodPatch, "/api/v1/dul/update/"+dulIDStr, body)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
if len(createCalls) != 2 {
|
||||
t.Fatalf("expected 2 create attachment calls after update, got %d", len(createCalls))
|
||||
}
|
||||
if !bytes.Equal(createCalls[1].FileID, fileID2) {
|
||||
t.Fatalf("expected second attachment file id = file_uuid[0]")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("update still supports image_attachment_ids for backward compatibility", func(t *testing.T) {
|
||||
fileID3 := uuidv7.MustBytes()
|
||||
fileID3Str, _ := uuidv7.BytesToString(fileID3)
|
||||
body := []byte(`{"data":{"type":"dul_update","attributes":{"image_attachment_ids":["` + fileID3Str + `"]}}}`)
|
||||
resp, _ := doReqDUL(t, app, http.MethodPatch, "/api/v1/dul/update/"+dulIDStr, body)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
if len(createCalls) != 3 {
|
||||
t.Fatalf("expected 3 create attachment calls after backward-compatible update, got %d", len(createCalls))
|
||||
}
|
||||
if !bytes.Equal(createCalls[2].FileID, fileID3) {
|
||||
t.Fatalf("expected third attachment file id from image_attachment_ids fallback")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDULHandler_CreateRejectsNotReadyFileBeforeCreatingRow(t *testing.T) {
|
||||
baseID := uuidv7.MustBytes()
|
||||
baseIDStr, _ := uuidv7.BytesToString(baseID)
|
||||
fileID := uuidv7.MustBytes()
|
||||
fileIDStr, _ := uuidv7.BytesToString(fileID)
|
||||
created := false
|
||||
|
||||
svc := &dulServiceMock{
|
||||
createFn: func(context.Context, *dul.DUL) error {
|
||||
created = true
|
||||
t.Fatal("DUL create must not be called when file is not ready")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
mockFM := &fileManagerAttachmentHandlerServiceMock{
|
||||
getAttachmentFn: func(context.Context, []byte) (*filemanager.Attachment, error) {
|
||||
return nil, nil
|
||||
},
|
||||
getFileFn: func(_ context.Context, id []byte) (*filemanager.File, error) {
|
||||
if !bytes.Equal(id, fileID) {
|
||||
t.Fatalf("unexpected file id: %x", id)
|
||||
}
|
||||
return &filemanager.File{ID: append([]byte(nil), id...), Status: filemanager.FileStatusProcessing}, nil
|
||||
},
|
||||
}
|
||||
|
||||
h := NewDULHandler(svc).WithFileManagerService(mockFM)
|
||||
app := newDULApp(h)
|
||||
|
||||
body := []byte(`{"data":{"type":"dul_create","attributes":{"name":"DUL A","date":"2026-05-27","base_id":"` + baseIDStr + `","file_uuid":["` + fileIDStr + `"]}}}`)
|
||||
resp, respBody := doReqDUL(t, app, http.MethodPost, "/api/v1/dul/create", body)
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 for not-ready file, got %d body=%v", resp.StatusCode, respBody)
|
||||
}
|
||||
if created {
|
||||
t.Fatal("expected DUL create to be skipped")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDULHandler_ListScope_AdminVsPilot(t *testing.T) {
|
||||
baseA := uuidv7.MustBytes()
|
||||
baseB := uuidv7.MustBytes()
|
||||
baseAHex := stringsLower(hex.EncodeToString(baseA))
|
||||
baseANameHex := stringsLower(hex.EncodeToString([]byte("Base A")))
|
||||
|
||||
svc := &dulServiceMock{
|
||||
listRows: []dul.DUL{},
|
||||
listTotal: 0,
|
||||
}
|
||||
bsvc := &baseServiceMock{
|
||||
listRows: []base.Base{
|
||||
{ID: baseA, BaseName: "Base A", DUL: true, BaseCategory: base.BaseCategory{Name: "hems"}},
|
||||
{ID: baseB, BaseName: "Base B", DUL: true, BaseCategory: base.BaseCategory{Name: "regular"}},
|
||||
},
|
||||
}
|
||||
|
||||
// Admin => scoped to all bases where dul=true.
|
||||
hAdmin := NewDULHandler(svc).WithBaseService(bsvc).WithContactService(&contactServiceMock{isAdmin: true})
|
||||
app := fiber.New()
|
||||
app.Get("/list", func(c *fiber.Ctx) error {
|
||||
c.Locals("user_id", uuidv7.MustBytes())
|
||||
return hAdmin.List(c)
|
||||
})
|
||||
req, _ := http.NewRequest(http.MethodGet, "/list", nil)
|
||||
resp, _ := app.Test(req)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("admin list status=%d", resp.StatusCode)
|
||||
}
|
||||
if len(svc.lastListID) != 2 {
|
||||
t.Fatalf("admin should see all dul bases, got scope size=%d", len(svc.lastListID))
|
||||
}
|
||||
|
||||
// Pilot => scoped only to DUL base roles.
|
||||
raw := baseAHex + "|" + baseANameHex + "|dul|" + stringsLower(hex.EncodeToString([]byte("hems")))
|
||||
contactItem := &contact.ContactListItem{
|
||||
RoleCode: "pilot",
|
||||
BaseRolesRaw: &raw,
|
||||
}
|
||||
hPilot := NewDULHandler(svc).WithBaseService(bsvc).WithContactService(&contactServiceMock{isAdmin: false, item: contactItem})
|
||||
app2 := fiber.New()
|
||||
app2.Get("/list", func(c *fiber.Ctx) error {
|
||||
c.Locals("user_id", uuidv7.MustBytes())
|
||||
return hPilot.List(c)
|
||||
})
|
||||
req2, _ := http.NewRequest(http.MethodGet, "/list", nil)
|
||||
resp2, _ := app2.Test(req2)
|
||||
if resp2.StatusCode != http.StatusOK {
|
||||
t.Fatalf("pilot list status=%d", resp2.StatusCode)
|
||||
}
|
||||
if len(svc.lastListID) != 1 {
|
||||
t.Fatalf("pilot should be scoped to one base, got %d", len(svc.lastListID))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDULHandler_ListScope_Rules(t *testing.T) {
|
||||
baseA := uuidv7.MustBytes()
|
||||
baseB := uuidv7.MustBytes()
|
||||
baseC := uuidv7.MustBytes()
|
||||
baseAHex := stringsLower(hex.EncodeToString(baseA))
|
||||
baseANameHex := stringsLower(hex.EncodeToString([]byte("Base A")))
|
||||
baseBHex := stringsLower(hex.EncodeToString(baseB))
|
||||
baseBNameHex := stringsLower(hex.EncodeToString([]byte("Base B")))
|
||||
makeApp := func(h *DULHandler) *fiber.App {
|
||||
app := fiber.New()
|
||||
app.Get("/list", func(c *fiber.Ctx) error {
|
||||
c.Locals("user_id", uuidv7.MustBytes())
|
||||
return h.List(c)
|
||||
})
|
||||
return app
|
||||
}
|
||||
newSvc := func() *dulServiceMock {
|
||||
return &dulServiceMock{listRows: []dul.DUL{}, listTotal: 0}
|
||||
}
|
||||
bsvc := &baseServiceMock{
|
||||
listRows: []base.Base{
|
||||
{ID: baseA, BaseName: "Base A", DUL: true, BaseCategory: base.BaseCategory{Name: "hems"}},
|
||||
{ID: baseB, BaseName: "Base B", DUL: true, BaseCategory: base.BaseCategory{Name: "regular"}},
|
||||
{ID: baseC, BaseName: "Base C", DUL: false, BaseCategory: base.BaseCategory{Name: "regular"}},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("admin sees all dul=true bases", func(t *testing.T) {
|
||||
svc := newSvc()
|
||||
h := NewDULHandler(svc).WithBaseService(bsvc).WithContactService(&contactServiceMock{isAdmin: true})
|
||||
resp, _ := makeApp(h).Test(httpTestReq(http.MethodGet, "/list"))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("admin list status=%d", resp.StatusCode)
|
||||
}
|
||||
if len(svc.lastListID) != 2 {
|
||||
t.Fatalf("expected admin scope size=2 dul bases, got %d", len(svc.lastListID))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("pilot sees only assigned dul bases", func(t *testing.T) {
|
||||
svc := newSvc()
|
||||
raw := baseAHex + "|" + baseANameHex + "|dul|" + stringsLower(hex.EncodeToString([]byte("hems"))) + "," +
|
||||
baseBHex + "|" + baseBNameHex + "|pilot|" + stringsLower(hex.EncodeToString([]byte("regular")))
|
||||
item := &contact.ContactListItem{RoleCode: "pilot", BaseRolesRaw: &raw}
|
||||
h := NewDULHandler(svc).WithBaseService(bsvc).WithContactService(&contactServiceMock{isAdmin: false, item: item})
|
||||
resp, _ := makeApp(h).Test(httpTestReq(http.MethodGet, "/list"))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("pilot list status=%d", resp.StatusCode)
|
||||
}
|
||||
if len(svc.lastListID) != 1 {
|
||||
t.Fatalf("expected pilot scope size=1 assigned dul base, got %d", len(svc.lastListID))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("pilot with no assigned dul base gets empty list scope", func(t *testing.T) {
|
||||
svc := newSvc()
|
||||
raw := baseBHex + "|" + baseBNameHex + "|pilot|" + stringsLower(hex.EncodeToString([]byte("regular")))
|
||||
item := &contact.ContactListItem{RoleCode: "pilot", BaseRolesRaw: &raw}
|
||||
h := NewDULHandler(svc).WithBaseService(bsvc).WithContactService(&contactServiceMock{isAdmin: false, item: item})
|
||||
resp, _ := makeApp(h).Test(httpTestReq(http.MethodGet, "/list"))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("pilot list status=%d", resp.StatusCode)
|
||||
}
|
||||
if len(svc.lastListID) != 0 {
|
||||
t.Fatalf("expected pilot with no dul assignment scope size=0, got %d", len(svc.lastListID))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("non pilot falls back to all dul=true bases", func(t *testing.T) {
|
||||
svc := newSvc()
|
||||
item := &contact.ContactListItem{RoleCode: "staff"}
|
||||
h := NewDULHandler(svc).WithBaseService(bsvc).WithContactService(&contactServiceMock{isAdmin: false, item: item})
|
||||
resp, _ := makeApp(h).Test(httpTestReq(http.MethodGet, "/list"))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("staff list status=%d", resp.StatusCode)
|
||||
}
|
||||
if len(svc.lastListID) != 2 {
|
||||
t.Fatalf("expected staff scope size=2 dul bases, got %d", len(svc.lastListID))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("without contact service falls back to all dul=true bases", func(t *testing.T) {
|
||||
svc := newSvc()
|
||||
h := NewDULHandler(svc).WithBaseService(bsvc)
|
||||
resp, _ := makeApp(h).Test(httpTestReq(http.MethodGet, "/list"))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("list status=%d", resp.StatusCode)
|
||||
}
|
||||
if len(svc.lastListID) != 2 {
|
||||
t.Fatalf("expected scope size=2 dul bases without contact service, got %d", len(svc.lastListID))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("base service error returns 400", func(t *testing.T) {
|
||||
svc := newSvc()
|
||||
h := NewDULHandler(svc).WithBaseService(&baseServiceMock{listErr: errors.New("base boom")}).WithContactService(&contactServiceMock{isAdmin: true})
|
||||
resp, _ := makeApp(h).Test(httpTestReq(http.MethodGet, "/list"))
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 when base scope resolution fails, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("contact admin check error returns 400", func(t *testing.T) {
|
||||
svc := newSvc()
|
||||
h := NewDULHandler(svc).WithBaseService(bsvc).WithContactService(&contactServiceMock{adminErr: errors.New("admin check boom")})
|
||||
resp, _ := makeApp(h).Test(httpTestReq(http.MethodGet, "/list"))
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 when admin check fails, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDULHandler_ValidationAndHelpers(t *testing.T) {
|
||||
svc := &dulServiceMock{createErr: errors.New("boom"), getResult: nil}
|
||||
h := NewDULHandler(svc)
|
||||
app := newDULApp(h)
|
||||
|
||||
resp, body := doReqDUL(t, app, http.MethodPost, "/api/v1/dul/create", []byte(`{`))
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 invalid json, got %d", resp.StatusCode)
|
||||
}
|
||||
errs, _ := body["errors"].([]any)
|
||||
if len(errs) == 0 {
|
||||
t.Fatalf("expected error payload")
|
||||
}
|
||||
|
||||
if _, err := parseDULDate("2026-05-25"); err != nil {
|
||||
t.Fatalf("expected valid date")
|
||||
}
|
||||
if _, err := parseDULDate("25-05-2026"); err == nil {
|
||||
t.Fatalf("expected invalid date")
|
||||
}
|
||||
|
||||
code, _ := inferDULErrorCode(fiber.StatusBadRequest, "Invalid JSON", "bad")
|
||||
if code != "DUL_INVALID_JSON" {
|
||||
t.Fatalf("unexpected code: %s", code)
|
||||
}
|
||||
code, _ = inferDULErrorCode(fiber.StatusUnprocessableEntity, "Validation error", "date must use format YYYY-MM-DD")
|
||||
if code != "DUL_DATE_INVALID" {
|
||||
t.Fatalf("unexpected code for date: %s", code)
|
||||
}
|
||||
}
|
||||
|
||||
func stringsLower(v string) string { return strings.ToLower(v) }
|
||||
|
||||
func TestDULHelperFunctions(t *testing.T) {
|
||||
t.Run("constructor chains", func(t *testing.T) {
|
||||
h := NewDULHandler(&dulServiceMock{})
|
||||
h = h.WithBaseService(&baseServiceMock{})
|
||||
h = h.WithContactService(&contactServiceMock{})
|
||||
h = h.WithFileManagerService(nil)
|
||||
h = h.WithFileStorage(nil)
|
||||
if h == nil {
|
||||
t.Fatalf("expected handler not nil")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("toResource and image mapping", func(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
row := &dul.DUL{
|
||||
ID: uuidv7.MustBytes(),
|
||||
BaseID: uuidv7.MustBytes(),
|
||||
Name: "DUL A",
|
||||
Date: time.Date(2026, 5, 25, 0, 0, 0, 0, time.UTC),
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
No: 3,
|
||||
Base: &base.Base{BaseName: "Halim", BaseCategory: base.BaseCategory{Name: "hems"}},
|
||||
Images: []*filemanager.Attachment{
|
||||
{ID: uuidv7.MustBytes()},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
res := NewDULHandler(&dulServiceMock{}).toResource(row)
|
||||
if res.Type != "dul" || res.Attributes.Base.Name != "Halim" || len(res.Attributes.Images) != 1 {
|
||||
t.Fatalf("unexpected mapped resource")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("inferDULErrorCode branches", func(t *testing.T) {
|
||||
cases := []struct {
|
||||
status int
|
||||
title string
|
||||
detail string
|
||||
}{
|
||||
{fiber.StatusNotFound, "", ""},
|
||||
{fiber.StatusUnprocessableEntity, "Validation error", "date invalid"},
|
||||
{fiber.StatusUnprocessableEntity, "Validation error", "invalid uuid"},
|
||||
{fiber.StatusUnprocessableEntity, "Validation error", "something"},
|
||||
{fiber.StatusBadRequest, "Invalid JSON", ""},
|
||||
{fiber.StatusBadRequest, "Create failed", ""},
|
||||
{fiber.StatusBadRequest, "Update failed", ""},
|
||||
{fiber.StatusBadRequest, "Delete failed", ""},
|
||||
{fiber.StatusBadRequest, "List failed", ""},
|
||||
{fiber.StatusInternalServerError, "", ""},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
code, errCode := inferDULErrorCode(tc.status, tc.title, tc.detail)
|
||||
if strings.TrimSpace(code) == "" || strings.TrimSpace(errCode) == "" {
|
||||
t.Fatalf("expected code for status=%d title=%q", tc.status, tc.title)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("isPilotRoleContact and parseDULBaseIDsFromRaw", func(t *testing.T) {
|
||||
if isPilotRoleContact(nil) {
|
||||
t.Fatalf("nil item should be false")
|
||||
}
|
||||
item := &contact.ContactListItem{RoleCode: "pilot"}
|
||||
if !isPilotRoleContact(item) {
|
||||
t.Fatalf("role_code pilot should be true")
|
||||
}
|
||||
rawRoles := "x|y|doctor," + mustRoleRawContact(t, uuidv7.MustBytes(), "Pilot", "pilot")
|
||||
item2 := &contact.ContactListItem{RolesRaw: &rawRoles}
|
||||
if !isPilotRoleContact(item2) {
|
||||
t.Fatalf("roles raw pilot should be true")
|
||||
}
|
||||
|
||||
baseID := uuidv7.MustBytes()
|
||||
baseHex := stringsLower(hex.EncodeToString(baseID))
|
||||
nameHex := stringsLower(hex.EncodeToString([]byte("Base A")))
|
||||
raw := baseHex + "|" + nameHex + "|dul," + baseHex + "|" + nameHex + "|dul"
|
||||
ids, set := parseDULBaseIDsFromRaw(&raw)
|
||||
if len(ids) != 1 || len(set) != 1 {
|
||||
t.Fatalf("expected deduped dul base ids")
|
||||
}
|
||||
bad := "invalid"
|
||||
ids, set = parseDULBaseIDsFromRaw(&bad)
|
||||
if len(ids) != 0 || len(set) != 0 {
|
||||
t.Fatalf("invalid raw should produce empty ids")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("syncImages branches", func(t *testing.T) {
|
||||
dulID := uuidv7.MustBytes()
|
||||
existingID := uuidv7.MustBytes()
|
||||
newFileID := uuidv7.MustBytes()
|
||||
newAttachmentID := uuidv7.MustBytes()
|
||||
created := 0
|
||||
|
||||
mock := &fileManagerAttachmentHandlerServiceMock{
|
||||
listAttachmentsFn: func(context.Context, filemanager.ListAttachmentsParams) ([]filemanager.Attachment, int64, error) {
|
||||
return []filemanager.Attachment{{ID: existingID}}, 1, nil
|
||||
},
|
||||
deleteAttachmentFn: func(context.Context, []byte) error { return nil },
|
||||
getAttachmentFn: func(_ context.Context, _ []byte) (*filemanager.Attachment, error) {
|
||||
return &filemanager.Attachment{ID: newAttachmentID, FileID: newFileID}, nil
|
||||
},
|
||||
createAttachmentFn: func(context.Context, filemanager.CreateAttachmentParams) (*filemanager.Attachment, error) {
|
||||
created++
|
||||
return &filemanager.Attachment{ID: uuidv7.MustBytes()}, nil
|
||||
},
|
||||
}
|
||||
h := NewDULHandler(&dulServiceMock{}).WithFileManagerService(mock)
|
||||
|
||||
app := fiber.New()
|
||||
app.Get("/ok", func(c *fiber.Ctx) error {
|
||||
c.Locals("user_id", uuidv7.MustBytes())
|
||||
raw := mustUUIDStringContact(t, newAttachmentID)
|
||||
if err := h.syncImages(c, dulID, []string{raw}); err != nil {
|
||||
t.Fatalf("unexpected syncImages err: %v", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
resp, err := app.Test(httpTestReq(http.MethodGet, "/ok"))
|
||||
if err != nil || resp.StatusCode != http.StatusOK || created != 1 {
|
||||
t.Fatalf("syncImages success not executed")
|
||||
}
|
||||
|
||||
app2 := fiber.New()
|
||||
app2.Get("/bad", func(c *fiber.Ctx) error {
|
||||
err := h.syncImages(c, dulID, []string{"bad-uuid"})
|
||||
if err == nil {
|
||||
t.Fatalf("expected invalid uuid error")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if _, err := app2.Test(httpTestReq(http.MethodGet, "/bad")); err != nil {
|
||||
t.Fatalf("unexpected app2 err: %v", err)
|
||||
}
|
||||
|
||||
t.Run("resubmit existing attachment uuid on update", func(t *testing.T) {
|
||||
// FE pulls the DUL via GET (which returns image.uuid = attachment id),
|
||||
// then PATCHes back the same uuid in image_attachment_ids. The bug we
|
||||
// fixed deleted the attachment first, then could not resolve the now-gone
|
||||
// uuid back to a file id and returned "image_attachment_ids[0] not found".
|
||||
existingAttID := uuidv7.MustBytes()
|
||||
existingFileID := uuidv7.MustBytes()
|
||||
deletedIDs := make([][]byte, 0, 1)
|
||||
createCalls := make([]filemanager.CreateAttachmentParams, 0, 1)
|
||||
lookupOrder := make([]string, 0, 2)
|
||||
|
||||
mock := &fileManagerAttachmentHandlerServiceMock{
|
||||
listAttachmentsFn: func(context.Context, filemanager.ListAttachmentsParams) ([]filemanager.Attachment, int64, error) {
|
||||
lookupOrder = append(lookupOrder, "list")
|
||||
return []filemanager.Attachment{{ID: existingAttID, FileID: existingFileID}}, 1, nil
|
||||
},
|
||||
deleteAttachmentFn: func(_ context.Context, id []byte) error {
|
||||
lookupOrder = append(lookupOrder, "delete")
|
||||
deletedIDs = append(deletedIDs, append([]byte(nil), id...))
|
||||
return nil
|
||||
},
|
||||
getAttachmentFn: func(_ context.Context, id []byte) (*filemanager.Attachment, error) {
|
||||
lookupOrder = append(lookupOrder, "getAtt")
|
||||
if bytes.Equal(id, existingAttID) {
|
||||
return &filemanager.Attachment{ID: existingAttID, FileID: existingFileID}, nil
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
getFileFn: func(_ context.Context, _ []byte) (*filemanager.File, error) {
|
||||
lookupOrder = append(lookupOrder, "getFile")
|
||||
return nil, nil
|
||||
},
|
||||
createAttachmentFn: func(_ context.Context, p filemanager.CreateAttachmentParams) (*filemanager.Attachment, error) {
|
||||
lookupOrder = append(lookupOrder, "create")
|
||||
createCalls = append(createCalls, p)
|
||||
return &filemanager.Attachment{ID: uuidv7.MustBytes(), FileID: append([]byte(nil), p.FileID...)}, nil
|
||||
},
|
||||
}
|
||||
h := NewDULHandler(&dulServiceMock{}).WithFileManagerService(mock)
|
||||
dulID := uuidv7.MustBytes()
|
||||
|
||||
app := fiber.New()
|
||||
app.Get("/resubmit", func(c *fiber.Ctx) error {
|
||||
c.Locals("user_id", uuidv7.MustBytes())
|
||||
raw := mustUUIDStringContact(t, existingAttID)
|
||||
if err := h.syncImages(c, dulID, []string{raw}); err != nil {
|
||||
t.Fatalf("syncImages must succeed when resubmitting existing attachment uuid: %v", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
resp, err := app.Test(httpTestReq(http.MethodGet, "/resubmit"))
|
||||
if err != nil || resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("unexpected resubmit response: err=%v status=%d", err, resp.StatusCode)
|
||||
}
|
||||
if len(createCalls) != 1 {
|
||||
t.Fatalf("expected exactly one CreateAttachment call, got %d", len(createCalls))
|
||||
}
|
||||
if !bytes.Equal(createCalls[0].FileID, existingFileID) {
|
||||
t.Fatalf("CreateAttachment must reuse the original file id")
|
||||
}
|
||||
if len(deletedIDs) != 1 || !bytes.Equal(deletedIDs[0], existingAttID) {
|
||||
t.Fatalf("expected existing attachment to be deleted exactly once, got %v", deletedIDs)
|
||||
}
|
||||
// The lookup must happen BEFORE the delete; otherwise GetAttachmentByID
|
||||
// would race with the delete and return nil.
|
||||
getIdx, delIdx := -1, -1
|
||||
for i, step := range lookupOrder {
|
||||
if step == "getAtt" && getIdx == -1 {
|
||||
getIdx = i
|
||||
}
|
||||
if step == "delete" && delIdx == -1 {
|
||||
delIdx = i
|
||||
}
|
||||
}
|
||||
if getIdx == -1 || delIdx == -1 || getIdx > delIdx {
|
||||
t.Fatalf("expected GetAttachmentByID before DeleteAttachment, lookup order=%v", lookupOrder)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("dedupe resubmitted file ids", func(t *testing.T) {
|
||||
// Sending the same file id twice must not violate the
|
||||
// (ref_type, ref_id, file_id) unique constraint at the attachments table.
|
||||
fileID := uuidv7.MustBytes()
|
||||
createCount := 0
|
||||
mock := &fileManagerAttachmentHandlerServiceMock{
|
||||
listAttachmentsFn: func(context.Context, filemanager.ListAttachmentsParams) ([]filemanager.Attachment, int64, error) {
|
||||
return nil, 0, nil
|
||||
},
|
||||
getAttachmentFn: func(_ context.Context, _ []byte) (*filemanager.Attachment, error) { return nil, nil },
|
||||
getFileFn: func(_ context.Context, id []byte) (*filemanager.File, error) {
|
||||
return &filemanager.File{ID: append([]byte(nil), id...), Status: filemanager.FileStatusReady}, nil
|
||||
},
|
||||
createAttachmentFn: func(_ context.Context, _ filemanager.CreateAttachmentParams) (*filemanager.Attachment, error) {
|
||||
createCount++
|
||||
return &filemanager.Attachment{ID: uuidv7.MustBytes()}, nil
|
||||
},
|
||||
}
|
||||
h := NewDULHandler(&dulServiceMock{}).WithFileManagerService(mock)
|
||||
dulID := uuidv7.MustBytes()
|
||||
app := fiber.New()
|
||||
app.Get("/dedupe", func(c *fiber.Ctx) error {
|
||||
c.Locals("user_id", uuidv7.MustBytes())
|
||||
raw := mustUUIDStringContact(t, fileID)
|
||||
if err := h.syncImages(c, dulID, []string{raw, raw}); err != nil {
|
||||
t.Fatalf("unexpected err on duplicate ids: %v", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if _, err := app.Test(httpTestReq(http.MethodGet, "/dedupe")); err != nil {
|
||||
t.Fatalf("unexpected dedupe err: %v", err)
|
||||
}
|
||||
if createCount != 1 {
|
||||
t.Fatalf("duplicate ids must collapse to one CreateAttachment call, got %d", createCount)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("accept file uuid fallback", func(t *testing.T) {
|
||||
createdFallback := 0
|
||||
mockFallback := &fileManagerAttachmentHandlerServiceMock{
|
||||
listAttachmentsFn: func(context.Context, filemanager.ListAttachmentsParams) ([]filemanager.Attachment, int64, error) {
|
||||
return nil, 0, nil
|
||||
},
|
||||
getAttachmentFn: func(_ context.Context, _ []byte) (*filemanager.Attachment, error) {
|
||||
return nil, nil
|
||||
},
|
||||
getFileFn: func(_ context.Context, id []byte) (*filemanager.File, error) {
|
||||
return &filemanager.File{ID: append([]byte(nil), id...), Status: filemanager.FileStatusReady}, nil
|
||||
},
|
||||
createAttachmentFn: func(_ context.Context, p filemanager.CreateAttachmentParams) (*filemanager.Attachment, error) {
|
||||
createdFallback++
|
||||
return &filemanager.Attachment{ID: uuidv7.MustBytes(), FileID: append([]byte(nil), p.FileID...)}, nil
|
||||
},
|
||||
}
|
||||
hFallback := NewDULHandler(&dulServiceMock{}).WithFileManagerService(mockFallback)
|
||||
appFallback := fiber.New()
|
||||
appFallback.Get("/file-fallback", func(c *fiber.Ctx) error {
|
||||
c.Locals("user_id", uuidv7.MustBytes())
|
||||
raw := mustUUIDStringContact(t, newFileID)
|
||||
if err := hFallback.syncImages(c, dulID, []string{raw}); err != nil {
|
||||
t.Fatalf("unexpected fallback err: %v", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
respFallback, err := appFallback.Test(httpTestReq(http.MethodGet, "/file-fallback"))
|
||||
if err != nil || respFallback.StatusCode != http.StatusOK || createdFallback != 1 {
|
||||
t.Fatalf("fallback path not executed")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDULEventsStream(t *testing.T) {
|
||||
hub := filemanagerrealtime.NewHub(1, nil)
|
||||
actor := uuidv7.MustBytes()
|
||||
userUUID, _ := uuidv7.BytesToString(actor)
|
||||
|
||||
app := fiber.New()
|
||||
fctx := &fasthttp.RequestCtx{}
|
||||
fctx.Request.SetRequestURI("/api/v1/dul/events")
|
||||
c := app.AcquireCtx(fctx)
|
||||
c.Locals("user_id", actor)
|
||||
if err := NewFileManagerEventsHandler(hub).Stream(c); err != nil {
|
||||
t.Fatalf("unexpected stream setup error: %v", err)
|
||||
}
|
||||
|
||||
hub.PublishToUsers([]string{userUUID}, filemanagerrealtime.FileStatusEvent{
|
||||
FileUUID: mustUUIDStringContact(t, uuidv7.MustBytes()),
|
||||
Status: "ready",
|
||||
Name: "dul-image.png",
|
||||
MimeType: "image/png",
|
||||
UpdatedAt: time.Now().UTC().Format(time.RFC3339),
|
||||
})
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
closeFirstHubSubscriberForUser(hub, userUUID)
|
||||
}()
|
||||
|
||||
bodyStr := c.Context().Response.String()
|
||||
app.ReleaseCtx(c)
|
||||
if !strings.Contains(bodyStr, ": connected") {
|
||||
t.Fatalf("expected connected comment in body: %q", bodyStr)
|
||||
}
|
||||
if !strings.Contains(bodyStr, "event: file.updated") {
|
||||
t.Fatalf("expected file.updated event in body: %q", bodyStr)
|
||||
}
|
||||
if !strings.Contains(bodyStr, "\"file_id\"") || !strings.Contains(bodyStr, "\"status\":\"ready\"") {
|
||||
t.Fatalf("expected file.updated payload in body: %q", bodyStr)
|
||||
}
|
||||
}
|
||||
|
||||
func httpTestReq(method, path string) *http.Request {
|
||||
req, _ := http.NewRequest(method, path, nil)
|
||||
return req
|
||||
}
|
||||
Reference in New Issue
Block a user