Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/csm-portal/backend/internal/handler/cases.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ func (h *CaseHandler) CreateCaseComment(w http.ResponseWriter, r *http.Request)

// Work notes are internal-only and exempt from the state gate.
var reqMeta struct {
Type string `json:"type"`
Type string `json:"typeKey"`
}
_ = json.Unmarshal(body, &reqMeta) // body is already validated JSON

Expand Down Expand Up @@ -514,7 +514,7 @@ func (h *CaseHandler) GetCaseAttachmentContent(w http.ResponseWriter, r *http.Re
}

// PatchCase handles PATCH /cases/{id}.
// Accepts state, priority, watchList, or assigneeEmail and forwards to the entity service.
// Accepts stateKey, priorityKey, workStateKey, watchList, or assigneeEmail and forwards to the entity service.
func (h *CaseHandler) PatchCase(w http.ResponseWriter, r *http.Request) {
user := middleware.UserInfoFromContext(r.Context())
if user == nil {
Expand Down Expand Up @@ -550,8 +550,8 @@ func (h *CaseHandler) PatchCase(w http.ResponseWriter, r *http.Request) {

// Validate state transition and workState guard before forwarding to the entity service.
var patch struct {
State *string `json:"state"`
WorkState *string `json:"workState"`
State *string `json:"stateKey"`
WorkState *string `json:"workStateKey"`
}
if err := json.Unmarshal(body, &patch); err == nil && (patch.State != nil || patch.WorkState != nil) {
current, err := h.entity.GetCase(r.Context(), caseID)
Expand Down
18 changes: 9 additions & 9 deletions apps/csm-portal/backend/internal/handler/cases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func upstreamErrors(fallback string) []upstreamErrorCase {
// ----- CreateCase -----

func TestCreateCase(t *testing.T) {
const validPayload = `{"projectId":"proj-1","deploymentId":"dep-1","deployedProductId":"dp-1","subject":"Login failure","description":"Users cannot log in","priority":"high","issueType":"error"}`
const validPayload = `{"projectId":"proj-1","deploymentId":"dep-1","deployedProductId":"dp-1","subject":"Login failure","description":"Users cannot log in","priorityKey":"high","issueTypeKey":"error"}`

t.Run("requires authenticated user", func(t *testing.T) {
h := NewCaseHandler(&mockEntityCaseClient{})
Expand Down Expand Up @@ -167,7 +167,7 @@ func TestCreateCase(t *testing.T) {
// ----- CreateCaseComment -----

func TestCreateCaseComment(t *testing.T) {
const validPayload = `{"type":"comment","content":"Looking into this now."}`
const validPayload = `{"typeKey":"comment","content":"Looking into this now."}`

t.Run("requires authenticated user", func(t *testing.T) {
h := NewCaseHandler(&mockEntityCaseClient{})
Expand Down Expand Up @@ -280,7 +280,7 @@ func TestCreateCaseComment(t *testing.T) {
},
}
h := NewCaseHandler(client)
r := withUser(httptest.NewRequest(http.MethodPost, "/cases/case-1/comments", strings.NewReader(`{"type":"work_note","content":"internal note"}`)))
r := withUser(httptest.NewRequest(http.MethodPost, "/cases/case-1/comments", strings.NewReader(`{"typeKey":"work_note","content":"internal note"}`)))
r.SetPathValue("id", "case-1")
w := httptest.NewRecorder()
h.CreateCaseComment(w, r)
Expand Down Expand Up @@ -406,7 +406,7 @@ func TestSearchCaseComments(t *testing.T) {
searchCaseCommentsFn: func(_ context.Context, caseID string, body []byte) ([]byte, error) {
capturedCaseID = caseID
capturedBody = body
return []byte(`{"comments":[{"id":"c-1","caseId":"case-42","commentType":"comment","body":"First comment","createdBy":"user-1","createdAt":"2026-06-03T00:00:00Z"}],"total":1,"limit":20,"offset":0,"hasMore":false}`), nil
return []byte(`{"comments":[{"id":"c-1","caseId":"case-42","type":"comment","content":"First comment","createdBy":"user-1","createdOn":"2026-06-03T00:00:00Z"}],"total":1,"limit":20,"offset":0,"hasMore":false}`), nil
},
}
h := NewCaseHandler(client)
Expand Down Expand Up @@ -576,7 +576,7 @@ func TestSearchCases(t *testing.T) {

func TestPatchCase(t *testing.T) {
const testCaseID = "11111111-1111-1111-1111-111111111111"
const validPayload = `{"state":"work_in_progress"}`
const validPayload = `{"stateKey":"work_in_progress"}`

t.Run("requires authenticated user", func(t *testing.T) {
h := NewCaseHandler(&mockEntityCaseClient{})
Expand Down Expand Up @@ -721,7 +721,7 @@ func TestPatchCase(t *testing.T) {
},
}
h := NewCaseHandler(client)
r := withUser(httptest.NewRequest(http.MethodPatch, "/cases/"+testCaseID, strings.NewReader(`{"state":"work_in_progress"}`)))
r := withUser(httptest.NewRequest(http.MethodPatch, "/cases/"+testCaseID, strings.NewReader(`{"stateKey":"work_in_progress"}`)))
r.SetPathValue("id", testCaseID)
w := httptest.NewRecorder()
h.PatchCase(w, r)
Expand All @@ -742,7 +742,7 @@ func TestPatchCase(t *testing.T) {
},
}
h := NewCaseHandler(client)
r := withUser(httptest.NewRequest(http.MethodPatch, "/cases/"+testCaseID, strings.NewReader(`{"workState":"ongoing"}`)))
r := withUser(httptest.NewRequest(http.MethodPatch, "/cases/"+testCaseID, strings.NewReader(`{"workStateKey":"ongoing"}`)))
r.SetPathValue("id", testCaseID)
w := httptest.NewRecorder()
h.PatchCase(w, r)
Expand All @@ -764,7 +764,7 @@ func TestPatchCase(t *testing.T) {
},
}
h := NewCaseHandler(client)
r := withUser(httptest.NewRequest(http.MethodPatch, "/cases/"+testCaseID, strings.NewReader(`{"workState":"paused"}`)))
r := withUser(httptest.NewRequest(http.MethodPatch, "/cases/"+testCaseID, strings.NewReader(`{"workStateKey":"paused"}`)))
r.SetPathValue("id", testCaseID)
w := httptest.NewRecorder()
h.PatchCase(w, r)
Expand All @@ -779,7 +779,7 @@ func TestPatchCase(t *testing.T) {
},
}
h := NewCaseHandler(client)
r := withUser(httptest.NewRequest(http.MethodPatch, "/cases/"+testCaseID, strings.NewReader(`{"priority":"high"}`)))
r := withUser(httptest.NewRequest(http.MethodPatch, "/cases/"+testCaseID, strings.NewReader(`{"priorityKey":"high"}`)))
r.SetPathValue("id", testCaseID)
w := httptest.NewRecorder()
h.PatchCase(w, r)
Expand Down
32 changes: 16 additions & 16 deletions apps/csm-portal/backend/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1284,24 +1284,24 @@ components:
UpdateCaseRequest:
type: object
description: |
Exactly one of `state`, `priority`, `workState`, `watchList`, or `assigneeEmail` must be provided.
Exactly one of `stateKey`, `priorityKey`, `workStateKey`, `watchList`, or `assigneeEmail` must be provided.
`watchList` and `assigneeEmail` are supported only for the ServiceNow data source.
oneOf:
- required: [state]
- required: [priority]
- required: [workState]
- required: [stateKey]
- required: [priorityKey]
- required: [workStateKey]
- required: [watchList]
- required: [assigneeEmail]
properties:
state:
stateKey:
type: string
enum: [open, work_in_progress, waiting_on_wso2, awaiting_info, reopened, solution_proposed, closed]
description: The new state to transition the case to.
priority:
priorityKey:
type: string
enum: [catastrophic, critical, high, medium, low]
description: The new priority (severity) of the case.
workState:
workStateKey:
type: string
enum: [ongoing, paused]
description: The new work sub-state of the case. Only applicable when the case state is work_in_progress.
Expand Down Expand Up @@ -1386,8 +1386,8 @@ components:
- deployedProductId
- subject
- description
- priority
- issueType
- priorityKey
- issueTypeKey
properties:
projectId:
type: string
Expand All @@ -1404,11 +1404,11 @@ components:
description:
type: string
description: Full description of the issue
priority:
priorityKey:
type: string
enum: [catastrophic, critical, high, medium, low]
description: Urgency level of the case
issueType:
issueTypeKey:
type: string
enum: [error, partial_outage, performance_degradation, question, security_or_compliance, total_outage]
description: Nature of the issue
Expand Down Expand Up @@ -2304,14 +2304,14 @@ components:
CaseCommentCreatePayload:
type: object
required:
- type
- body
- typeKey
- content
properties:
type:
typeKey:
type: string
enum: [work_note, comment, activity]
description: work_note is restricted to internal users; activity to internal and system users
body:
content:
type: string
description: Comment text

Expand All @@ -2325,7 +2325,7 @@ components:
type:
type: string
enum: [work_note, comment, activity]
body:
content:
type: string
createdBy:
type: string
Expand Down