Skip to content

Commit 15be6ab

Browse files
authored
feat: implement risk Accept/ Review endpoints (#345)
* feat: risk accept/review endpoints * fix: copilot issues * Increase integration test runs * Add validation for extend review * Run gh-address-comments skill * Fix open PR comments
1 parent 0cfb541 commit 15be6ab

File tree

10 files changed

+1582
-5
lines changed

10 files changed

+1582
-5
lines changed

Makefile

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ IMG ?= controller:latest
2222
ENVTEST_K8S_VERSION = 1.26.1
2323
# Default Test Path for a single integration test. Defaults to root
2424
TEST_PATH ?= ./...
25+
# Number of times to run integration tests (set >1 to hunt flakes)
26+
INTEGRATION_RUNS ?= 1
2527

2628
BLUE := $(shell printf "\033[34m")
2729
YELLOW := $(shell printf "\033[33m")
@@ -63,11 +65,18 @@ test: swag ## Run tests
6365
$(OK) Tests passed
6466

6567
.PHONY: test-integration
66-
test-integration: swag ## Run tests
67-
@if ! go test ./... -coverprofile cover.out -v --tags integration; then \
68-
$(WARN) "Tests failed"; \
69-
exit 1; \
70-
fi ; \
68+
test-integration: swag ## Run integration tests (set INTEGRATION_RUNS>1 for flakiness detection)
69+
@for run in $$(seq 1 $(INTEGRATION_RUNS)); do \
70+
$(INFO) "Integration run $$run/$(INTEGRATION_RUNS)"; \
71+
coverprofile_flag=""; \
72+
if [ "$$run" -eq "$(INTEGRATION_RUNS)" ]; then \
73+
coverprofile_flag="-coverprofile cover.out"; \
74+
fi; \
75+
if ! go test ./... -count=1 $$coverprofile_flag -v --tags integration; then \
76+
$(WARN) "Tests failed on run $$run"; \
77+
exit 1; \
78+
fi ; \
79+
done ; \
7180
$(OK) Tests passed
7281

7382

docs/docs.go

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17648,6 +17648,70 @@ const docTemplate = `{
1764817648
]
1764917649
}
1765017650
},
17651+
"/risks/{id}/accept": {
17652+
"post": {
17653+
"description": "Accepts a risk with required justification and a future review deadline.",
17654+
"consumes": [
17655+
"application/json"
17656+
],
17657+
"produces": [
17658+
"application/json"
17659+
],
17660+
"tags": [
17661+
"Risks"
17662+
],
17663+
"summary": "Accept risk",
17664+
"parameters": [
17665+
{
17666+
"type": "string",
17667+
"description": "Risk ID",
17668+
"name": "id",
17669+
"in": "path",
17670+
"required": true
17671+
},
17672+
{
17673+
"description": "Accept payload",
17674+
"name": "body",
17675+
"in": "body",
17676+
"required": true,
17677+
"schema": {
17678+
"$ref": "#/definitions/handler.acceptRiskRequest"
17679+
}
17680+
}
17681+
],
17682+
"responses": {
17683+
"200": {
17684+
"description": "OK",
17685+
"schema": {
17686+
"$ref": "#/definitions/handler.GenericDataResponse-handler_riskResponse"
17687+
}
17688+
},
17689+
"400": {
17690+
"description": "Bad Request",
17691+
"schema": {
17692+
"$ref": "#/definitions/api.Error"
17693+
}
17694+
},
17695+
"404": {
17696+
"description": "Not Found",
17697+
"schema": {
17698+
"$ref": "#/definitions/api.Error"
17699+
}
17700+
},
17701+
"500": {
17702+
"description": "Internal Server Error",
17703+
"schema": {
17704+
"$ref": "#/definitions/api.Error"
17705+
}
17706+
}
17707+
},
17708+
"security": [
17709+
{
17710+
"OAuth2Password": []
17711+
}
17712+
]
17713+
}
17714+
},
1765117715
"/risks/{id}/components": {
1765217716
"get": {
1765317717
"description": "Lists components linked to a risk.",
@@ -18079,6 +18143,70 @@ const docTemplate = `{
1807918143
]
1808018144
}
1808118145
},
18146+
"/risks/{id}/review": {
18147+
"post": {
18148+
"description": "Records a structured review for an accepted risk. nextReviewDeadline is required for decision=extend and must be omitted for decision=reopen.",
18149+
"consumes": [
18150+
"application/json"
18151+
],
18152+
"produces": [
18153+
"application/json"
18154+
],
18155+
"tags": [
18156+
"Risks"
18157+
],
18158+
"summary": "Review risk",
18159+
"parameters": [
18160+
{
18161+
"type": "string",
18162+
"description": "Risk ID",
18163+
"name": "id",
18164+
"in": "path",
18165+
"required": true
18166+
},
18167+
{
18168+
"description": "Review payload",
18169+
"name": "body",
18170+
"in": "body",
18171+
"required": true,
18172+
"schema": {
18173+
"$ref": "#/definitions/handler.reviewRiskRequest"
18174+
}
18175+
}
18176+
],
18177+
"responses": {
18178+
"200": {
18179+
"description": "OK",
18180+
"schema": {
18181+
"$ref": "#/definitions/handler.GenericDataResponse-handler_riskResponse"
18182+
}
18183+
},
18184+
"400": {
18185+
"description": "Bad Request",
18186+
"schema": {
18187+
"$ref": "#/definitions/api.Error"
18188+
}
18189+
},
18190+
"404": {
18191+
"description": "Not Found",
18192+
"schema": {
18193+
"$ref": "#/definitions/api.Error"
18194+
}
18195+
},
18196+
"500": {
18197+
"description": "Internal Server Error",
18198+
"schema": {
18199+
"$ref": "#/definitions/api.Error"
18200+
}
18201+
}
18202+
},
18203+
"security": [
18204+
{
18205+
"OAuth2Password": []
18206+
}
18207+
]
18208+
}
18209+
},
1808218210
"/risks/{id}/subjects": {
1808318211
"get": {
1808418212
"description": "Lists subjects linked to a risk.",
@@ -18570,6 +18698,148 @@ const docTemplate = `{
1857018698
]
1857118699
}
1857218700
},
18701+
"/ssp/{sspId}/risks/{id}/accept": {
18702+
"post": {
18703+
"description": "Accepts a risk by ID scoped to an SSP.",
18704+
"consumes": [
18705+
"application/json"
18706+
],
18707+
"produces": [
18708+
"application/json"
18709+
],
18710+
"tags": [
18711+
"Risks"
18712+
],
18713+
"summary": "Accept risk for SSP",
18714+
"parameters": [
18715+
{
18716+
"type": "string",
18717+
"description": "SSP ID",
18718+
"name": "sspId",
18719+
"in": "path",
18720+
"required": true
18721+
},
18722+
{
18723+
"type": "string",
18724+
"description": "Risk ID",
18725+
"name": "id",
18726+
"in": "path",
18727+
"required": true
18728+
},
18729+
{
18730+
"description": "Accept payload",
18731+
"name": "body",
18732+
"in": "body",
18733+
"required": true,
18734+
"schema": {
18735+
"$ref": "#/definitions/handler.acceptRiskRequest"
18736+
}
18737+
}
18738+
],
18739+
"responses": {
18740+
"200": {
18741+
"description": "OK",
18742+
"schema": {
18743+
"$ref": "#/definitions/handler.GenericDataResponse-handler_riskResponse"
18744+
}
18745+
},
18746+
"400": {
18747+
"description": "Bad Request",
18748+
"schema": {
18749+
"$ref": "#/definitions/api.Error"
18750+
}
18751+
},
18752+
"404": {
18753+
"description": "Not Found",
18754+
"schema": {
18755+
"$ref": "#/definitions/api.Error"
18756+
}
18757+
},
18758+
"500": {
18759+
"description": "Internal Server Error",
18760+
"schema": {
18761+
"$ref": "#/definitions/api.Error"
18762+
}
18763+
}
18764+
},
18765+
"security": [
18766+
{
18767+
"OAuth2Password": []
18768+
}
18769+
]
18770+
}
18771+
},
18772+
"/ssp/{sspId}/risks/{id}/review": {
18773+
"post": {
18774+
"description": "Records a risk review by ID scoped to an SSP. nextReviewDeadline is required for decision=extend and must be omitted for decision=reopen.",
18775+
"consumes": [
18776+
"application/json"
18777+
],
18778+
"produces": [
18779+
"application/json"
18780+
],
18781+
"tags": [
18782+
"Risks"
18783+
],
18784+
"summary": "Review risk for SSP",
18785+
"parameters": [
18786+
{
18787+
"type": "string",
18788+
"description": "SSP ID",
18789+
"name": "sspId",
18790+
"in": "path",
18791+
"required": true
18792+
},
18793+
{
18794+
"type": "string",
18795+
"description": "Risk ID",
18796+
"name": "id",
18797+
"in": "path",
18798+
"required": true
18799+
},
18800+
{
18801+
"description": "Review payload",
18802+
"name": "body",
18803+
"in": "body",
18804+
"required": true,
18805+
"schema": {
18806+
"$ref": "#/definitions/handler.reviewRiskRequest"
18807+
}
18808+
}
18809+
],
18810+
"responses": {
18811+
"200": {
18812+
"description": "OK",
18813+
"schema": {
18814+
"$ref": "#/definitions/handler.GenericDataResponse-handler_riskResponse"
18815+
}
18816+
},
18817+
"400": {
18818+
"description": "Bad Request",
18819+
"schema": {
18820+
"$ref": "#/definitions/api.Error"
18821+
}
18822+
},
18823+
"404": {
18824+
"description": "Not Found",
18825+
"schema": {
18826+
"$ref": "#/definitions/api.Error"
18827+
}
18828+
},
18829+
"500": {
18830+
"description": "Internal Server Error",
18831+
"schema": {
18832+
"$ref": "#/definitions/api.Error"
18833+
}
18834+
}
18835+
},
18836+
"security": [
18837+
{
18838+
"OAuth2Password": []
18839+
}
18840+
]
18841+
}
18842+
},
1857318843
"/subject-templates": {
1857418844
"get": {
1857518845
"description": "List subject templates with optional filters and pagination.",
@@ -24067,6 +24337,17 @@ const docTemplate = `{
2406724337
"handler.UserHandler": {
2406824338
"type": "object"
2406924339
},
24340+
"handler.acceptRiskRequest": {
24341+
"type": "object",
24342+
"properties": {
24343+
"justification": {
24344+
"type": "string"
24345+
},
24346+
"reviewDeadline": {
24347+
"type": "string"
24348+
}
24349+
}
24350+
},
2407024351
"handler.addComponentLinkRequest": {
2407124352
"type": "object",
2407224353
"properties": {
@@ -24173,6 +24454,23 @@ const docTemplate = `{
2417324454
}
2417424455
}
2417524456
},
24457+
"handler.reviewRiskRequest": {
24458+
"type": "object",
24459+
"properties": {
24460+
"decision": {
24461+
"type": "string"
24462+
},
24463+
"nextReviewDeadline": {
24464+
"type": "string"
24465+
},
24466+
"notes": {
24467+
"type": "string"
24468+
},
24469+
"reviewedAt": {
24470+
"type": "string"
24471+
}
24472+
}
24473+
},
2417624474
"handler.riskControlLinkResponse": {
2417724475
"type": "object",
2417824476
"properties": {

0 commit comments

Comments
 (0)