diff --git a/.circleci/config.yml b/.circleci/config.yml index 40089eb..f890c28 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,12 +1,12 @@ version: 2.1 setup: true orbs: - qqq-orb: kingsrook/qqq-orb@0.7.16 + qqq-orb: kingsrook/qqq-orb@0.8 workflows: setup: jobs: - qqq-orb/generate_pipeline: - orb_version: 0.7.16 + orb_version: '0.8' filters: branches: ignore: automation/qbit-rc-pending diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..ce00351 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,7 @@ +name: build +on: + push: +jobs: + maven: + uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1 + secrets: inherit diff --git a/.qqq-ci.yml b/.qqq-ci.yml index 9431668..6777ede 100644 --- a/.qqq-ci.yml +++ b/.qqq-ci.yml @@ -1,5 +1,5 @@ profile: qbit -orb_version: 0.7.16 +orb_version: '0.8' default_branch: develop java_version: '21' browsers: true @@ -13,3 +13,4 @@ publish: release_candidate: true release: true asciidoc: false +release_completion: true diff --git a/pom.xml b/pom.xml index edc35c8..e4881ac 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ + 4.1.0-RC.26 0.1.0-RC.1 21 21 @@ -29,7 +30,7 @@ com.kingsrook qbit-build-parent - 2.0.0 + 2.2.1 com.kingsrook.qbits @@ -246,7 +247,7 @@ true - 4.1.0-RC.11 + 4.1.0-RC.26 diff --git a/src/main/resources/openapi/qqq-v1/qa-capture.yaml b/src/main/resources/openapi/qqq-v1/qa-capture.yaml index efdf5d9..5bfdd15 100644 --- a/src/main/resources/openapi/qqq-v1/qa-capture.yaml +++ b/src/main/resources/openapi/qqq-v1/qa-capture.yaml @@ -1,5 +1,44 @@ components: schemas: + BasicErrorResponseV1: + description: "The error body of every UI API operation (core and extensions)." + properties: + error: + description: "The error message (the same as message; kept for clients of\ + \ UI API 1.0 to 1.5)." + type: "string" + code: + description: "A stable, machine-readable code. Well-known values: BAD_REQUEST\ + \ (400), UNAUTHENTICATED (401),\nPERMISSION_DENIED (403), NOT_FOUND (404),\ + \ CONFLICT (409), RATE_LIMITED (429), INTERNAL_ERROR (500),\nERROR (other).\ + \ Extensions may add codes; treat an unknown code by the HTTP status.\ + \ Since 1.6.0." + type: "string" + message: + description: "A message to show the user. Since 1.6.0." + type: "string" + fieldErrors: + description: "Errors about particular request fields, when known. Since\ + \ 1.6.0." + items: + properties: + field: + description: "The field (or parameter) name." + type: "string" + message: + description: "The error about that field." + type: "string" + code: + description: "An optional machine-readable code, e.g. REQUIRED, TOO_LONG,\ + \ PATTERN." + type: "string" + type: "object" + type: "array" + traceId: + description: "Identifies this request in the server logs (also the X-QQQ-Trace-Id\ + \ response header); quote it when reporting a problem. Since 1.6.0." + type: "string" + type: "object" QaCaptureCapabilityV1: description: "What QA capture means for the signed-in user. Clients that see\ \ enabled=false do nothing else." @@ -237,9 +276,42 @@ components: type: "object" securitySchemes: sessionUuidCookie: + description: "The session cookie set by POST /qqq/v1/manageSession (web clients)." in: "cookie" name: "sessionUUID" type: "apiKey" + bearerAuth: + bearerFormat: "JWT or session UUID" + description: "`Authorization: Bearer `, where the token is either\n-\ + \ an OAuth2 access token (a signed JWT) issued by the app's identity provider\ + \ to the app's client,\n or to its native client (`values.nativeClientId`\ + \ in GET /qqq/v1/metaData/authentication), for\n example from the device-authorization\ + \ grant or auth-code + PKCE. It is verified against the\n provider's keys\ + \ (issuer, audience, expiry, signature) and gives the same user and permissions\n\ + \ as signing in on the web. When it expires (401), refresh it at the provider\ + \ and retry; or\n- a session UUID returned by POST /qqq/v1/manageSession,\ + \ which the server keeps refreshed." + scheme: "bearer" + type: "http" + oauth2: + description: "The app's OAuth2 / OpenID Connect provider. The real endpoints\ + \ come from the provider's discovery\ndocument: `{values.baseUrl}.well-known/openid-configuration`,\ + \ with values from\nGET /qqq/v1/metaData/authentication. Native clients use\ + \ `values.nativeClientId` (a public client)\nwith auth-code + PKCE (loopback\ + \ or custom-scheme redirect URI registered at the provider) or the\ndevice-authorization\ + \ grant (`device_authorization_endpoint`, RFC 8628), then send the access\ + \ token\nas `Authorization: Bearer`." + flows: + authorizationCode: + authorizationUrl: "/authorize" + scopes: + email: "Email address" + offline_access: "A refresh token, to renew the access token without signing\ + \ in again" + openid: "OpenID Connect sign-in" + profile: "Name, username and groups (permissions come from the groups)" + tokenUrl: "/token" + type: "oauth2" info: contact: email: "contact@kingsrook.com" @@ -280,10 +352,37 @@ paths: schema: $ref: "#/components/schemas/QaCaptureCapabilityV1" description: "Successful Response" + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Read the QA capture capability" + tags: + - "qa-capture" /qqq/v1/qbit/qa-capture/report: post: description: "Stores the report (page, app, table, record, process, recent request\ @@ -322,7 +421,40 @@ paths: schema: $ref: "#/components/schemas/QaCaptureReportResultV1" description: "Successful Response" + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Create a QA report" + tags: + - "qa-capture" +servers: +- description: "The QQQ server that serves this document (paths include /qqq/v1)." + url: "/" +tags: +- description: "Operations of the qa-capture extension." + name: "qa-capture" diff --git a/src/test/java/com/kingsrook/qbits/qacapture/QaCaptureApiContractTest.java b/src/test/java/com/kingsrook/qbits/qacapture/QaCaptureApiContractTest.java index 5cd0444..da8a2c0 100644 --- a/src/test/java/com/kingsrook/qbits/qacapture/QaCaptureApiContractTest.java +++ b/src/test/java/com/kingsrook/qbits/qacapture/QaCaptureApiContractTest.java @@ -58,7 +58,7 @@ class QaCaptureApiContractTest extends BaseQaCaptureTest "./mvnw -Dtest=QaCaptureApiContractTest -Dqacapture.openapi.update=true test")).isEmpty(); var model = version.generateExtensionOpenAPIModel("qqq", extension); - assertThat(model.getComponents().getSchemas().keySet()).allMatch(name -> name.startsWith("QaCapture")); + assertThat(model.getComponents().getSchemas().keySet()).allMatch(name -> name.startsWith("QaCapture") || name.equals("BasicErrorResponseV1")); assertThat(model.getPaths().keySet()).containsExactlyInAnyOrder("/qqq/v1/qbit/qa-capture/capability", "/qqq/v1/qbit/qa-capture/report"); assertThat(extension.getSemanticVersion()).isEqualTo("1.0.0"); assertThat(extension.getEffectivePathPrefix()).isEqualTo("/qbit/qa-capture"); diff --git a/src/test/java/com/kingsrook/qbits/qacapture/QaCaptureServiceTest.java b/src/test/java/com/kingsrook/qbits/qacapture/QaCaptureServiceTest.java index 0001cb8..9ae1816 100644 --- a/src/test/java/com/kingsrook/qbits/qacapture/QaCaptureServiceTest.java +++ b/src/test/java/com/kingsrook/qbits/qacapture/QaCaptureServiceTest.java @@ -248,7 +248,7 @@ class QaCaptureServiceTest extends BaseQaCaptureTest QaCaptureCsp.customize(headers); assertThat(headers.getSources("script-src")).containsExactly("'self'", "https://js.jam.dev"); assertThat(headers.getSources("frame-src")).containsExactly("'self'", "https://*.jam.dev"); - assertThat(headers.getSources("connect-src")).containsExactly("'self'", "https://*.jam.dev"); + assertThat(headers.getSources("connect-src")).containsExactly("'self'", "blob:", "https://*.jam.dev"); /////////////////////////////////////////////////////////////////// // a broken database is logged and leaves the headers unchanged //