diff --git a/admin-app/src/main/resources/openapi.yml b/admin-app/src/main/resources/openapi.yml index 818882c6a..6d517867b 100644 --- a/admin-app/src/main/resources/openapi.yml +++ b/admin-app/src/main/resources/openapi.yml @@ -7,6 +7,8 @@ paths: /api/group-settings/{groupId}: get: summary: Get group settings + description: | + Retrieves the configuration settings for a specific application group. operationId: getGroupSettingsById tags: - settings @@ -16,6 +18,7 @@ paths: - name: groupId in: path required: true + description: Unique identifier for the application group whose settings are being retrieved. schema: type: string responses: @@ -27,6 +30,9 @@ paths: $ref: '#/components/schemas/DataResponse' put: summary: Save group settings + description: | + Creates or updates configuration settings for a specific application group. + If settings already exist for the group, they are overwritten with the new values. operationId: updateGroupSettingsById tags: - settings @@ -36,6 +42,7 @@ paths: - name: groupId in: path required: true + description: Unique identifier for the application group whose settings are being saved. schema: type: string requestBody: @@ -53,6 +60,9 @@ paths: $ref: '#/components/schemas/MessageResponse' delete: summary: Delete group settings + description: | + Removes all configuration settings for a specific application group, resetting them to system defaults. + After deletion, the group will use the global default values. operationId: deleteGroupSettingsById tags: - settings @@ -62,6 +72,7 @@ paths: - name: groupId in: path required: true + description: Unique identifier for the application group whose settings are being deleted. schema: type: string responses: @@ -71,12 +82,11 @@ paths: # Data Ingest Endpoints /api/data-ingest/builds: put: - summary: Persist application build metadata + summary: Persist application build identity description: | - Saves application build information, including build version and associated Git commit metadata. - This endpoint captures critical build data required for change tracking, impact analysis, risk assessment, and - correlation with test coverage metrics. The service ingests build metadata from instrumented applications to enable - detailed analysis of code changes, test recommendations, and coverage reports across different application versions. + Saves application build identity information, including groupId, appId, commitSha, and buildVersion. + This endpoint only persists the build identity fields. To save additional Git metadata (branch, commitDate, + commitMessage, commitAuthor), use the PUT /api/data-ingest/builds/info endpoint. operationId: putBuild tags: - data-ingest @@ -98,6 +108,35 @@ paths: application/json: schema: $ref: '#/components/schemas/MessageResponse' + /api/data-ingest/builds/info: + put: + summary: Persist application build Git metadata + description: | + Saves Git metadata (branch, commitDate, commitMessage, commitAuthor) for an application build. + The build is identified by groupId, appId, commitSha, and buildVersion. If a build with the given + identity does not exist, a new build is created. If it already exists, only the Git metadata fields + are updated while preserving the existing identity and instance data. + operationId: putBuildInfo + tags: + - data-ingest + security: + - apiKeyAuth: [ ] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BuildPayload' + application/protobuf: + schema: + $ref: '#/components/schemas/BuildPayload' + responses: + '200': + description: Build info saved + content: + application/json: + schema: + $ref: '#/components/schemas/MessageResponse' /api/data-ingest/instances: put: summary: Persist application instance metadata @@ -302,6 +341,13 @@ paths: /api/data-ingest/method-ignore-rules: post: summary: Save method ignore rule + description: | + Creates a new rule for excluding methods from coverage analysis and metrics computation. + Rules are matched by name pattern and/or class name pattern using glob-style matching. + + **Note:** Newly created rules are automatically applied during subsequent ETL cycles for new data only. + If you need the rule to take effect for already loaded data, trigger an ETL refresh from scratch + by calling `POST /api/metrics/refresh?reset=true`. operationId: postMethodIgnoreRules tags: - data-ingest @@ -325,6 +371,14 @@ paths: $ref: '#/components/schemas/MessageResponse' get: summary: Get all method ignore rules + description: | + Retrieves the complete list of method ignore rules configured in the system. + Each rule defines patterns (name and/or class name) used to exclude matching methods + from coverage analysis and metrics computation. + + **Note:** Changes to ignore rules (creation or deletion) are automatically applied during subsequent ETL cycles + for new data only. If you need updated rules to take effect for already loaded data, trigger an ETL refresh + from scratch by calling `POST /api/metrics/refresh?reset=true`. operationId: getAllMethodIgnoreRules tags: - data-ingest @@ -340,6 +394,11 @@ paths: /api/data-ingest/method-ignore-rules/{id}: delete: summary: Delete method ignore rule by ID + description: | + Removes a specific method ignore rule by its unique identifier. + + **Note:** After deleting a rule, previously excluded methods will only reappear in metrics after + an ETL refresh from scratch. Trigger it by calling `POST /api/metrics/refresh?reset=true`. operationId: deleteMethodIgnoreRuleById tags: - data-ingest @@ -349,6 +408,7 @@ paths: - name: id in: path required: true + description: Unique numeric identifier of the method ignore rule to delete. schema: type: integer responses: @@ -362,6 +422,9 @@ paths: /api/data-management/groups/{groupId}/apps/{appId}/builds/{buildId}: delete: summary: Delete build data with all associated data (coverage, methods, instances) + description: | + Permanently deletes a specific application build and all its associated data, including code coverage records, + method metadata, and instance deployments. Use this endpoint to clean up obsolete builds or free storage. operationId: deleteBuild tags: - data-management @@ -371,16 +434,19 @@ paths: - name: groupId in: path required: true + description: Unique identifier for the application group containing the build. schema: type: string - name: appId in: path required: true + description: Unique identifier for the application containing the build. schema: type: string - name: buildId in: path required: true + description: Unique identifier of the build to delete. This is the internal build ID assigned by the system. schema: type: string responses: @@ -393,6 +459,10 @@ paths: /api/data-management/groups/{groupId}/tests/sessions/{testSessionId}: delete: summary: Delete test session data with all associated data (coverage, test launches) + description: | + Permanently deletes a specific test session and all its associated data, including test launch records + and code coverage data collected during the session. Use this endpoint to clean up + invalid or obsolete test sessions. operationId: deleteTestSession tags: - data-management @@ -402,11 +472,13 @@ paths: - name: groupId in: path required: true + description: Unique identifier for the application group containing the test session. schema: type: string - name: testSessionId in: path required: true + description: Unique identifier of the test session to delete. schema: type: string responses: @@ -420,6 +492,8 @@ paths: /api/metrics/applications: get: summary: Get applications + description: | + Retrieves a list of all registered applications in Drill4J. operationId: getApplications tags: - metrics @@ -429,6 +503,7 @@ paths: - name: groupId in: query required: false + description: Optional filter to retrieve applications belonging to a specific group only. schema: type: string responses: @@ -441,6 +516,8 @@ paths: /api/metrics/builds: get: summary: Get builds + description: | + Retrieves a paginated list of application builds for a specific application. operationId: getBuilds tags: - metrics @@ -450,27 +527,33 @@ paths: - name: groupId in: query required: true + description: Unique identifier for the application group. schema: type: string - name: appId in: query required: true + description: Unique identifier for the application. schema: type: string - name: branch in: query + description: Optional Git branch name filter. When specified, only builds from this branch are returned. schema: type: string - name: envId in: query + description: Optional environment identifier filter. When specified, only builds deployed in this environment are returned. schema: type: string - name: page in: query + description: Page number for pagination (1-based). Defaults to 1 if not specified. schema: type: integer - name: pageSize in: query + description: Number of items per page. Defaults to a system-defined value if not specified. schema: type: integer responses: @@ -483,6 +566,12 @@ paths: /api/metrics/build-diff-report: get: summary: Get build diff report + description: | + Generates a comprehensive diff report comparing a target build against a baseline build. The report includes + summary statistics on code changes (new, modified, deleted methods), overall and per-change-type coverage percentages, + risk assessment based on uncovered changes. The target build is identified by + instanceId, commitSha, or buildVersion; the baseline build is identified by baselineInstanceId, baselineCommitSha, + or baselineBuildVersion. operationId: getBuildDiffReport tags: - metrics @@ -492,39 +581,48 @@ paths: - name: groupId in: query required: true + description: Unique identifier for the application group. schema: type: string - name: appId in: query required: true + description: Unique identifier for the application. schema: type: string - name: instanceId in: query + description: Instance ID of the target build deployment. schema: type: string - name: commitSha in: query + description: Git commit SHA of the target build. schema: type: string - name: buildVersion in: query + description: Build version of the target build. schema: type: string - name: baselineInstanceId in: query + description: Instance ID of the baseline build deployment. schema: type: string - name: baselineCommitSha in: query + description: Git commit SHA of the baseline build. schema: type: string - name: baselineBuildVersion in: query + description: Build version of the baseline build. schema: type: string - name: coverageThreshold in: query + description: Minimum required coverage percentage (0.0–1.0) for the diff report quality gate. schema: type: number format: float @@ -539,6 +637,14 @@ paths: /api/metrics/recommended-tests: get: summary: Get recommended tests + description: | + Analyzes code changes between a target build and a baseline build and returns a list of tests recommended + for execution based on impact analysis. The endpoint identifies which methods have changed and finds tests + that previously covered those methods, recommending them for re-execution to validate the changes. + When testsToSkip is true, the endpoint inverts the result - returning tests that can be safely skipped + because they do not cover any changed code. The target build is identified by + targetInstanceId, targetCommitSha, or targetBuildVersion. The baseline build is identified by + baselineInstanceId, baselineCommitSha, baselineBuildVersion, or baselineBuildBranches. operationId: getRecommendedTests tags: - metrics @@ -548,55 +654,66 @@ paths: - name: groupId in: query required: true + description: Unique identifier for the application group. schema: type: string - name: appId in: query required: true + description: Unique identifier for the application. schema: type: string - name: testsToSkip in: query + description: When true, returns tests that can be safely skipped (not impacted by changes). When false (default), returns tests that should be executed because they cover changed code. schema: type: boolean default: false - name: testTaskId in: query + description: Optional test task ID filter. When specified, only considers test launches associated with this task ID for recommendations. schema: type: string - name: targetInstanceId in: query + description: Instance ID of the target build. One of targetInstanceId, targetCommitSha, or targetBuildVersion should be provided. schema: type: string - name: targetCommitSha in: query + description: Git commit SHA of the target build. One of targetInstanceId, targetCommitSha, or targetBuildVersion should be provided. schema: type: string - name: targetBuildVersion in: query + description: Build version of the target build. One of targetInstanceId, targetCommitSha, or targetBuildVersion should be provided. schema: type: string - name: baselineInstanceId in: query + description: Instance ID of the baseline build. If not provided, the system automatically selects the previous build as baseline. schema: type: string - name: baselineCommitSha in: query + description: Git commit SHA of the baseline build. If not provided, the system automatically selects the previous build as baseline. schema: type: string - name: baselineBuildVersion in: query + description: Build version of the baseline build. If not provided, the system automatically selects the previous build as baseline. schema: type: string - name: baselineBuildBranches in: query - description: List of baseline build branches to consider. + description: List of Git branch names to consider when selecting the baseline build. Useful when the baseline should be chosen from specific branches (e.g., main, develop). schema: type: array items: type: string - name: coveragePeriodDays in: query + description: Number of days to look back for coverage data when determining test recommendations. Limits the scope of historical coverage data used for analysis. schema: type: integer responses: @@ -609,6 +726,9 @@ paths: /api/metrics/coverage-treemap: get: summary: Get coverage treemap + description: | + Retrieves a hierarchical treemap representation of code coverage data for a specific build. The treemap + organizes methods into a package/class hierarchy, where each node contains aggregated coverage statistics. operationId: getCoverageTreemap tags: - metrics @@ -618,30 +738,37 @@ paths: - name: buildId in: query required: true + description: Internal build identifier for which to retrieve coverage treemap data. schema: type: string - name: testTag in: query + description: Optional test tag filter. When specified, only coverage data from tests with this tag is included. schema: type: string - name: envId in: query + description: Optional environment identifier filter. When specified, only coverage data from this environment is included. schema: type: string - name: branch in: query + description: Optional Git branch filter. When specified, only coverage data from this branch is included. schema: type: string - name: packageNamePattern in: query + description: Optional package name pattern filter for narrowing results to specific packages. schema: type: string - name: classNamePattern in: query + description: Optional class name pattern filter for narrowing results to specific classes. schema: type: string - name: rootId in: query + description: Optional root node identifier to retrieve a specific subtree of the treemap hierarchy. Used for drill-down navigation. schema: type: string responses: @@ -654,6 +781,9 @@ paths: /api/metrics/changes-coverage-treemap: get: summary: Get changes coverage treemap + description: | + Retrieves a hierarchical treemap representation of code coverage focused only on changed methods + between a target build and a baseline build. operationId: getChangesCoverageTreemap tags: - metrics @@ -663,51 +793,63 @@ paths: - name: buildId in: query required: true + description: Internal build identifier of the target (newer) build. schema: type: string - name: baselineBuildId in: query required: true + description: Internal build identifier of the baseline (older) build to compare against. schema: type: string - name: testTag in: query + description: Optional test tag filter. When specified, only coverage data from tests with this tag is included. schema: type: string - name: envId in: query + description: Optional environment identifier filter. When specified, only coverage data from this environment is included. schema: type: string - name: branch in: query + description: Optional Git branch filter. When specified, only coverage data from this branch is included. schema: type: string - name: packageNamePattern in: query + description: Optional package name pattern filter for narrowing results to specific packages. schema: type: string - name: classNamePattern in: query + description: Optional class name pattern filter for narrowing results to specific classes. schema: type: string - name: rootId in: query + description: Optional root node identifier to retrieve a specific subtree of the treemap hierarchy. schema: type: string - name: page in: query + description: Page number for pagination (1-based). Defaults to 1 if not specified. schema: type: integer - name: pageSize in: query + description: Number of items per page. Defaults to a system-defined value if not specified. schema: type: integer - name: includeDeleted in: query + description: Whether to include methods that were deleted in the target build (present in baseline but absent in target). Defaults to false. schema: type: boolean - name: includeEqual in: query + description: Whether to include methods that are unchanged between builds. Defaults to false. schema: type: boolean responses: @@ -720,6 +862,10 @@ paths: /api/metrics/changes: get: summary: Get method changes between builds + description: | + Retrieves a paginated list of method-level changes between a target build and a baseline build. + The target build is identified by instanceId, commitSha, or buildVersion; the baseline build is identified + by baselineInstanceId, baselineCommitSha, or baselineBuildVersion. operationId: getMethodChanges tags: - metrics @@ -729,51 +875,63 @@ paths: - name: groupId in: query required: true + description: Unique identifier for the application group. schema: type: string - name: appId in: query required: true + description: Unique identifier for the application. schema: type: string - name: instanceId in: query + description: Instance ID of the target build deployment. schema: type: string - name: commitSha in: query + description: Git commit SHA of the target build. schema: type: string - name: buildVersion in: query + description: Build version of the target build. schema: type: string - name: baselineInstanceId in: query + description: Instance ID of the baseline build deployment. schema: type: string - name: baselineCommitSha in: query + description: Git commit SHA of the baseline build. schema: type: string - name: baselineBuildVersion in: query + description: Build version of the baseline build. schema: type: string - name: includeDeleted in: query + description: Whether to include methods that were deleted in the target build (present in baseline but absent in target). Defaults to false. schema: type: boolean - name: includeEqual in: query + description: Whether to include methods that are unchanged between builds. Defaults to false. schema: type: boolean - name: page in: query + description: Page number for pagination (1-based). Defaults to 1 if not specified. schema: type: integer - name: pageSize in: query + description: Number of items per page. Defaults to a system-defined value if not specified. schema: type: integer responses: @@ -786,6 +944,8 @@ paths: /api/metrics/coverage: get: summary: Get coverage by methods for a build + description: | + Retrieves a paginated list of methods with their code coverage data for a specific build. operationId: getCoverageByMethods tags: - metrics @@ -795,34 +955,42 @@ paths: - name: buildId in: query required: true + description: Internal build identifier for which to retrieve method-level coverage data. schema: type: string - name: testTag in: query + description: Optional test tag filter. When specified, only coverage data from tests with this tag is included. schema: type: string - name: envId in: query + description: Optional environment identifier filter. When specified, only coverage data from this environment is included. schema: type: string - name: branch in: query + description: Optional Git branch filter. When specified, only coverage data from this branch is included. schema: type: string - name: packageNamePattern in: query + description: Optional package name pattern filter for narrowing results to specific packages. schema: type: string - name: classNamePattern in: query + description: Optional class name pattern filter for narrowing results to specific classes. schema: type: string - name: page in: query + description: Page number for pagination (1-based). Defaults to 1 if not specified. schema: type: integer - name: pageSize in: query + description: Number of items per page. Defaults to a system-defined value if not specified. schema: type: integer responses: @@ -835,6 +1003,9 @@ paths: /api/metrics/impacted-tests: get: summary: Get impacted tests + description: | + Retrieves a paginated list of tests that are impacted by code changes between a target build and a baseline build. + Impact analysis identifies which tests previously covered methods that have been modified, added, or deleted. operationId: getImpactedTests tags: - metrics @@ -844,68 +1015,92 @@ paths: - name: groupId in: query required: true + description: Unique identifier for the application group. schema: type: string - name: appId in: query required: true + description: Unique identifier for the application. schema: type: string - name: instanceId in: query + description: Instance ID of the target build deployment. schema: type: string - name: commitSha in: query + description: Git commit SHA of the target build. schema: type: string - name: buildVersion in: query + description: Build version of the target build. schema: type: string - name: baselineInstanceId in: query + description: Instance ID of the baseline build deployment. schema: type: string - name: baselineCommitSha in: query + description: Git commit SHA of the baseline build. schema: type: string - name: baselineBuildVersion in: query + description: Build version of the baseline build. schema: type: string - name: packageName in: query + description: Optional filter to only consider methods in this package when determining impacted tests. schema: type: string - name: className in: query + description: Optional filter to only consider methods in this class when determining impacted tests. schema: type: string - name: methodName in: query + description: Optional filter to only consider methods with this name when determining impacted tests. schema: type: string + - name: excludeMethodSignatures + in: query + description: List of method signatures to exclude from impact analysis. Tests covering only excluded methods will not be returned. Signature format is "className:methodName:params:returnType". + schema: + type: array + items: + type: string + style: form + explode: true - name: testTaskId in: query + description: Optional filter by test task ID. When specified, only tests from launches associated with this task ID are considered. schema: type: string - name: testTag in: query + description: Optional filter by test tag. When specified, only tests with this tag are included. schema: type: string - name: testPath in: query + description: Optional filter by test source path. When specified, only tests at this path are included. schema: type: string - name: testName in: query + description: Optional filter by test name. When specified, only tests with this name are included. schema: type: string - name: coverageBranches in: query - description: List of coverage branches to consider. + description: List of Git branch names to consider when selecting coverage data for impact analysis. schema: type: array items: @@ -914,16 +1109,7 @@ paths: explode: true - name: coverageAppEnvIds in: query - description: List of coverage app env IDs to consider. - schema: - type: array - items: - type: string - style: form - explode: true - - name: excludeMethodSignatures - in: query - description: List of method signatures to exclude from impact analysis. Tests covering only excluded methods will not be returned. Signature format is "className:methodName:params:returnType" (e.g., "com.example.Class:method1:():void"). + description: List of application environment IDs to consider when selecting coverage data for impact analysis. schema: type: array items: @@ -950,10 +1136,12 @@ paths: - DESC - name: page in: query + description: Page number for pagination (1-based). Defaults to 1 if not specified. schema: type: integer - name: pageSize in: query + description: Number of items per page. Defaults to a system-defined value if not specified. schema: type: integer responses: @@ -965,6 +1153,10 @@ paths: $ref: '#/components/schemas/ListDataResponse' post: summary: Get impacted tests (POST) + description: | + POST variant of the impacted tests endpoint. Accepts the same parameters as the GET endpoint but via a JSON + request body instead of query parameters. Useful when the number of filter parameters is large or contains + complex values (e.g., lists of method signatures to exclude) that are impractical to pass as query parameters. operationId: postImpactedTests tags: - metrics @@ -986,6 +1178,10 @@ paths: /api/metrics/impacted-methods: get: summary: Get impacted methods + description: | + Retrieves a paginated list of methods that have been covered by tests and changed between a target build and a baseline build. + While impacted-tests shows which tests are affected by changes, impacted-methods shows + which changed methods are covered (or uncovered) by existing tests. operationId: getImpactedMethods tags: - metrics @@ -995,83 +1191,80 @@ paths: - name: groupId in: query required: true + description: Unique identifier for the application group. schema: type: string - name: appId in: query required: true + description: Unique identifier for the application. schema: type: string - name: instanceId in: query + description: Instance ID of the target build deployment. schema: type: string - name: commitSha in: query + description: Git commit SHA of the target build. schema: type: string - name: buildVersion in: query + description: Build version of the target build. schema: type: string - name: baselineInstanceId in: query + description: Instance ID of the baseline build deployment. schema: type: string - name: baselineCommitSha in: query + description: Git commit SHA of the baseline build. schema: type: string - name: baselineBuildVersion in: query + description: Build version of the baseline build schema: type: string - name: packageName in: query + description: Optional filter by package name to narrow results to methods in a specific package. schema: type: string - name: className in: query + description: Optional filter by class name to narrow results to methods in a specific class. schema: type: string - name: methodName in: query + description: Optional filter by method name to narrow results to a specific method. schema: type: string - name: testTaskId in: query + description: Optional filter by test task ID. When specified, only test launches from this task ID are considered for impact counting. schema: type: string - name: testTag in: query + description: Optional filter by test tag. When specified, only tests with this tag are considered for impact counting. schema: type: string - name: testPath in: query + description: Optional filter by test source path. When specified, only tests at this path are considered for impact counting. schema: type: string - name: testName in: query + description: Optional filter by test name. When specified, only tests with this name are considered for impact counting. schema: type: string - - name: coverageBranches - in: query - description: List of coverage branches to consider. - schema: - type: array - items: - type: string - style: form - explode: true - - name: coverageAppEnvIds - in: query - description: List of coverage app env IDs to consider. - schema: - type: array - items: - type: string - style: form - explode: true - name: sortBy in: query description: Field name to sort results by. Supported values are signature, className, name, impactedTests. @@ -1092,10 +1285,12 @@ paths: - DESC - name: page in: query + description: Page number for pagination (1-based). Defaults to 1 if not specified. schema: type: integer - name: pageSize in: query + description: Number of items per page. Defaults to a system-defined value if not specified. schema: type: integer responses: @@ -1107,6 +1302,10 @@ paths: $ref: '#/components/schemas/ListDataResponse' post: summary: Get impacted methods (POST) + description: | + POST variant of the impacted methods endpoint. Accepts the same parameters as the GET endpoint but via a JSON + request body instead of query parameters. Useful when the number of filter parameters is large or contains + complex values that are impractical to pass as query parameters. operationId: postImpactedMethods tags: - metrics @@ -1128,6 +1327,8 @@ paths: /api/metrics/refresh: post: summary: Refresh metrics + description: | + Triggers an immediate ETL (Extract-Transform-Load) job to refresh computed metrics data. operationId: refreshMetrics tags: - metrics @@ -1156,6 +1357,10 @@ paths: /api/metrics/refresh-status: get: summary: Get refresh status + description: | + Retrieves the current status of the metrics ETL (Extract-Transform-Load) refresh process for a specific + application group. Returns information about whether a refresh is currently in progress, the last + successful refresh timestamp, and any error details if the last refresh failed. operationId: metricsRefreshStatus tags: - metrics @@ -1165,6 +1370,7 @@ paths: - name: groupId in: query required: true + description: Unique identifier for the application group whose refresh status is being queried. schema: type: string responses: @@ -1178,6 +1384,8 @@ paths: /api/sign-in: post: summary: Sign in + description: | + Authenticates a user with username and password credentials. On successful authentication, returns a JWT token. operationId: signIn tags: - auth @@ -1197,6 +1405,9 @@ paths: /api/sign-up: post: summary: Sign up + description: | + Registers a new user account in the system. The newly created account requires administrator approval + before it becomes active. operationId: signUp tags: - auth @@ -1216,6 +1427,8 @@ paths: /api/sign-out: post: summary: Sign out + description: | + Signs the current user out by clearing the JWT authentication cookie. operationId: signOut tags: - auth @@ -1229,6 +1442,9 @@ paths: /api/user-info: get: summary: Get user info + description: | + Retrieves profile information for the currently authenticated user, including username, role, and + registration status. operationId: getUserInfo tags: - auth @@ -1244,6 +1460,9 @@ paths: /api/update-password: post: summary: Update password + description: | + Changes the password for the currently authenticated user. The old password must be provided for verification + before the new password is set. operationId: updatePassword tags: - auth @@ -1266,6 +1485,8 @@ paths: /api/users: get: summary: Get users + description: | + Retrieves a list of all registered users in the system. operationId: getAllUsers tags: - auth @@ -1283,6 +1504,8 @@ paths: /api/users/{userId}: get: summary: Get user by ID + description: | + Retrieves detailed information for a specific user by their unique numeric identifier. operationId: getUserById tags: - auth @@ -1292,6 +1515,7 @@ paths: - name: userId in: path required: true + description: Unique numeric identifier of the user to retrieve. schema: type: integer responses: @@ -1303,6 +1527,8 @@ paths: $ref: '#/components/schemas/DataResponse' put: summary: Edit user + description: | + Updates the role of a specific user. operationId: editUserById tags: - auth @@ -1312,6 +1538,7 @@ paths: - name: userId in: path required: true + description: Unique numeric identifier of the user to edit. schema: type: integer requestBody: @@ -1329,6 +1556,8 @@ paths: $ref: '#/components/schemas/DataResponse' delete: summary: Delete user + description: | + Permanently deletes a specific user account from the system. operationId: deleteUserById tags: - auth @@ -1338,6 +1567,7 @@ paths: - name: userId in: path required: true + description: Unique numeric identifier of the user to delete. schema: type: integer responses: @@ -1350,6 +1580,8 @@ paths: /api/users/{userId}/block: patch: summary: Block user + description: | + Blocks a specific user account, preventing them from authenticating and accessing the system. operationId: blockUserById tags: - auth @@ -1359,6 +1591,7 @@ paths: - name: userId in: path required: true + description: Unique numeric identifier of the user to block. schema: type: integer responses: @@ -1371,6 +1604,8 @@ paths: /api/users/{userId}/unblock: patch: summary: Unblock user + description: | + Unblocks a previously blocked user account, restoring their ability to authenticate and access the system. operationId: unblockUserById tags: - auth @@ -1380,6 +1615,7 @@ paths: - name: userId in: path required: true + description: Unique numeric identifier of the user to unblock. schema: type: integer responses: @@ -1392,6 +1628,8 @@ paths: /api/users/{userId}/reset-password: patch: summary: Reset user password + description: | + Resets the password for a specific user to a new auto-generated password. operationId: resetUserPasswordById tags: - auth @@ -1401,6 +1639,7 @@ paths: - name: userId in: path required: true + description: Unique numeric identifier of the user whose password is being reset. schema: type: integer responses: @@ -1414,6 +1653,8 @@ paths: /api/user-keys: get: summary: Get user API keys + description: | + Retrieves all API keys owned by the currently authenticated user. operationId: getUserApiKeys tags: - auth @@ -1430,6 +1671,9 @@ paths: $ref: '#/components/schemas/ListDataResponse' post: summary: Generate user API key + description: | + Generates a new API key for the currently authenticated user. The API key value is returned only once in the response - it cannot be + retrieved again after creation, so it must be stored securely by the client. operationId: generateUserApiKey tags: - auth @@ -1451,6 +1695,8 @@ paths: /api/user-keys/{id}: delete: summary: Delete user API key + description: | + Deletes a specific API key owned by the currently authenticated user. operationId: deleteUserApiKeyById tags: - auth @@ -1460,6 +1706,7 @@ paths: - name: id in: path required: true + description: Unique numeric identifier of the API key to delete. schema: type: integer responses: @@ -1473,6 +1720,8 @@ paths: /api/api-keys: get: summary: Get API keys + description: | + Retrieves a list of all API keys across all users in the system. operationId: getApiKeys tags: - auth @@ -1489,6 +1738,8 @@ paths: $ref: '#/components/schemas/ListDataResponse' post: summary: Generate API key + description: | + Generates a new API key on behalf of any user. operationId: generateApiKey tags: - auth @@ -1510,6 +1761,8 @@ paths: /api/api-keys/{id}: delete: summary: Delete API key + description: | + Deletes any API key by its ID. operationId: deleteApiKeyById tags: - auth @@ -1519,6 +1772,7 @@ paths: - name: id in: path required: true + description: Unique numeric identifier of the API key to delete. schema: type: integer responses: @@ -1898,7 +2152,6 @@ components: description: Optional array of tags for categorizing and filtering tests (e.g., ['slow', 'database', 'critical'], ['@tag.one', '@tag.two']). Used for test selection and organization. items: type: string - default: [ ] metadata: type: object description: Optional json object for storing custom test metadata. Framework or project-specific information. @@ -1993,130 +2246,194 @@ components: description: Application build version identifier. Either commitSha or buildVersion must be provided to identify a build. MethodIgnoreRulePayload: type: object + description: | + Rule definition for excluding methods from coverage analysis and metrics computation. properties: groupId: type: string + description: Unique identifier for the application group this rule applies to. appId: type: string + description: Unique identifier for the application this rule applies to. namePattern: type: string nullable: true + description: Glob-style pattern for matching method names to exclude (e.g., 'get*', 'set*', 'toString'). Methods with names matching this pattern will be excluded from analysis. classnamePattern: type: string nullable: true + description: Glob-style pattern for matching fully qualified class names to exclude (e.g., 'com.example.generated.*'). All methods in classes matching this pattern will be excluded. # Auth Schemas LoginPayload: type: object + description: | + User authentication credentials for signing in to the system. + required: + - username + - password properties: username: type: string + description: The username of the account to authenticate. password: type: string + description: The password for the account. SignUpPayload: type: object + description: | + New user registration payload. Creates a user account that requires administrator approval before activation. + required: + - username + - password properties: username: type: string + description: Desired username for the new account. Must be unique in the system. password: type: string + description: Password for the new account. email: type: string + description: Optional email address for the new user. UpdatePasswordPayload: type: object + description: | + Password change request payload. The old password is verified before the new password is set. + required: + - oldPassword + - newPassword properties: oldPassword: type: string + description: The current password of the user. Must match the existing password for verification. newPassword: type: string + description: The new password to set for the user. EditUserPayload: type: object + description: | + Payload for editing user properties. Currently supports changing the user's role. + required: + - role properties: username: type: string + description: Updated username (currently not used for modification). email: type: string + description: Updated email address (currently not used for modification). role: type: string + description: New role for the user. Supported values are 'ADMIN' and 'USER'. + enum: + - ADMIN + - USER GenerateApiKeyPayload: type: object + description: | + Payload for generating a new API key. + required: + - name properties: name: type: string + description: Human-readable description or name for the API key (e.g., 'CI/CD pipeline key', 'Agent key for staging'). # Settings Schemas GroupSettingsPayload: type: object + description: | + Configuration settings for an application group. Controls data lifecycle policies and metrics computation scope. properties: retentionPeriodDays: type: integer nullable: true + description: Number of days to retain raw ingested data (builds, coverage, methods, test sessions) before automatic cleanup. Set to null to use the system default. metricsPeriodDays: type: integer nullable: true + description: Number of days of historical data to include when computing metrics and coverage aggregations. Set to null to use the system default. # Metrics Request Schemas ImpactedTestsRequest: type: object + description: | + Request body for the POST variant of the impacted tests endpoint. required: - groupId - appId properties: groupId: type: string + description: Unique identifier for the application group. appId: type: string + description: Unique identifier for the application. instanceId: type: string nullable: true + description: Instance ID of the target build. One of instanceId, commitSha, or buildVersion should be provided. commitSha: type: string nullable: true + description: Git commit SHA of the target build. One of instanceId, commitSha, or buildVersion should be provided. buildVersion: type: string nullable: true + description: Build version of the target build. One of instanceId, commitSha, or buildVersion should be provided. baselineInstanceId: type: string nullable: true + description: Instance ID of the baseline build. If not provided, the system automatically selects the previous build. baselineCommitSha: type: string nullable: true + description: Git commit SHA of the baseline build. If not provided, the system automatically selects the previous build. baselineBuildVersion: type: string nullable: true + description: Build version of the baseline build. If not provided, the system automatically selects the previous build. packageName: type: string nullable: true + description: Optional filter to only consider methods in this package when determining impacted tests. className: type: string nullable: true + description: Optional filter to only consider methods in this class when determining impacted tests. methodName: type: string nullable: true + description: Optional filter to only consider methods with this name when determining impacted tests. excludeMethodSignatures: type: array items: type: string - description: List of method signatures to exclude from impact analysis. Signature format is "className:methodName:params:returnType". + description: List of method signatures to exclude from impact analysis. Tests covering only excluded methods will not be returned. Signature format is "className:methodName:params:returnType". testTaskId: type: string nullable: true + description: Optional filter by test task ID. When specified, only tests from launches associated with this task ID are considered. testTag: type: string nullable: true + description: Optional filter by test tag. When specified, only tests with this tag are included. testPath: type: string nullable: true + description: Optional filter by test source path. When specified, only tests at this path are included. testName: type: string nullable: true + description: Optional filter by test name. When specified, only tests with this name are included. coverageBranches: type: array items: type: string - description: List of coverage branches to consider. + description: List of Git branch names to consider when selecting coverage data for impact analysis. coverageAppEnvIds: type: array items: type: string - description: List of coverage app env IDs to consider. + description: List of application environment IDs to consider when selecting coverage data for impact analysis. sortBy: type: string enum: @@ -2141,63 +2458,70 @@ components: nullable: true ImpactedMethodsRequest: type: object + description: | + Request body for the POST variant of the impacted methods endpoint. required: - groupId - appId properties: groupId: type: string + description: Unique identifier for the application group. appId: type: string + description: Unique identifier for the application. instanceId: type: string nullable: true + description: Instance ID of the target build. One of instanceId, commitSha, or buildVersion should be provided. commitSha: type: string nullable: true + description: Git commit SHA of the target build. One of instanceId, commitSha, or buildVersion should be provided. buildVersion: type: string nullable: true + description: Build version of the target build. One of instanceId, commitSha, or buildVersion should be provided. baselineInstanceId: type: string nullable: true + description: Instance ID of the baseline build. If not provided, the system automatically selects the previous build. baselineCommitSha: type: string nullable: true + description: Git commit SHA of the baseline build. If not provided, the system automatically selects the previous build. baselineBuildVersion: type: string nullable: true + description: Build version of the baseline build. If not provided, the system automatically selects the previous build. packageName: type: string nullable: true + description: Optional filter by package name to narrow results to methods in a specific package. className: type: string nullable: true + description: Optional filter by class name to narrow results to methods in a specific class. methodName: type: string nullable: true + description: Optional filter by method name to narrow results to a specific method. testTaskId: type: string nullable: true + description: Optional filter by test task ID. When specified, only test launches from this task ID are considered for impact counting. testTag: type: string nullable: true + description: Optional filter by test tag. When specified, only tests with this tag are considered for impact counting. testPath: type: string nullable: true + description: Optional filter by test source path. When specified, only tests at this path are considered for impact counting. testName: type: string nullable: true - coverageBranches: - type: array - items: - type: string - description: List of coverage branches to consider. - coverageAppEnvIds: - type: array - items: - type: string - description: List of coverage app env IDs to consider. + description: Optional filter by test name. When specified, only tests with this name are considered for impact counting. sortBy: type: string enum: diff --git a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/DataIngestClient.kt b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/DataIngestClient.kt index c3a0a4b9e..fb9f28d5a 100644 --- a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/DataIngestClient.kt +++ b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/DataIngestClient.kt @@ -98,7 +98,7 @@ val TestDetails.definitionId: String } suspend fun HttpClient.putBuild(payload: BuildPayload): HttpResponse { - return put("/data-ingest/builds") { + return put("/data-ingest/builds/info") { setBody(payload) }.assertSuccessStatus() } diff --git a/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/entity/Build.kt b/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/entity/Build.kt index f3cbd9535..a96cf6d12 100644 --- a/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/entity/Build.kt +++ b/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/entity/Build.kt @@ -23,9 +23,9 @@ class Build( val appId: String, val commitSha: String?, val buildVersion: String?, - val branch: String?, val instanceId: String?, - val commitDate: LocalDateTime?, - val commitMessage: String?, - val commitAuthor: String? + val branch: String? = null, + val commitDate: LocalDateTime? = null, + val commitMessage: String? = null, + val commitAuthor: String? = null ) diff --git a/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/repository/BuildRepository.kt b/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/repository/BuildRepository.kt index 8614384bf..fa8764c40 100644 --- a/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/repository/BuildRepository.kt +++ b/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/repository/BuildRepository.kt @@ -19,7 +19,8 @@ import com.epam.drill.admin.writer.rawdata.entity.Build import java.time.LocalDate interface BuildRepository { - suspend fun create(build: Build) + suspend fun saveBuildInfo(build: Build) + suspend fun saveBuildId(build: Build) suspend fun existsById(groupId: String, appId: String, buildId: String): Boolean suspend fun deleteAllCreatedBefore(groupId: String, createdBefore: LocalDate) suspend fun deleteByBuildId(groupId: String, appId: String, buildId: String) diff --git a/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/repository/impl/BuildRepositoryImpl.kt b/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/repository/impl/BuildRepositoryImpl.kt index b1f159d9c..bc16dd0fd 100644 --- a/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/repository/impl/BuildRepositoryImpl.kt +++ b/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/repository/impl/BuildRepositoryImpl.kt @@ -27,9 +27,11 @@ import org.jetbrains.exposed.sql.upsert import java.time.LocalDate class BuildRepositoryImpl: BuildRepository { - override suspend fun create(build: Build) { + override suspend fun saveBuildInfo(build: Build) { BuildTable.upsert( - onUpdateExclude = listOf(BuildTable.createdAt), + onUpdateExclude = listOf( + BuildTable.createdAt, + ), ) { it[id] = build.id it[groupId] = build.groupId @@ -44,6 +46,27 @@ class BuildRepositoryImpl: BuildRepository { it[updatedAt] = org.jetbrains.exposed.sql.javatime.CurrentDateTime } } + + override suspend fun saveBuildId(build: Build) { + BuildTable.upsert( + onUpdateExclude = listOf( + BuildTable.createdAt, + BuildTable.branch, + BuildTable.committedAt, + BuildTable.commitAuthor, + BuildTable.commitMessage + ), + ) { + it[id] = build.id + it[groupId] = build.groupId + it[appId] = build.appId + it[commitSha] = build.commitSha + it[buildVersion] = build.buildVersion + it[instanceId] = build.instanceId + it[updatedAt] = org.jetbrains.exposed.sql.javatime.CurrentDateTime + } + } + override suspend fun existsById(groupId: String, appId: String, buildId: String): Boolean { return BuildTable.selectAll().where { (BuildTable.groupId eq groupId) and diff --git a/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/route/RawDataWriterRoutes.kt b/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/route/RawDataWriterRoutes.kt index d02cf23f9..09b3bf4ad 100644 --- a/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/route/RawDataWriterRoutes.kt +++ b/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/route/RawDataWriterRoutes.kt @@ -49,6 +49,9 @@ private val logger = KotlinLogging.logger {} @Resource("builds") class BuildsRoute() +@Resource("builds/info") +class BuildsInfoRoute() + @Resource("instances") class InstancesRoute() @@ -81,6 +84,7 @@ class MethodIgnoreRulesRoute() { fun Route.dataIngestRoutes() { route("/data-ingest") { putBuilds() + putBuildsInfo() putInstances() postCoverage() putMethods() @@ -104,6 +108,15 @@ fun Route.putBuilds() { } } +fun Route.putBuildsInfo() { + val rawDataWriter by closestDI().instance() + + put { + rawDataWriter.saveBuildInfo(call.decompressAndReceive()) + call.ok("Build info saved") + } +} + fun Route.putInstances() { val rawDataWriter by closestDI().instance() diff --git a/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/service/RawDataWriter.kt b/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/service/RawDataWriter.kt index 853f4ffcb..ebdca25d3 100644 --- a/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/service/RawDataWriter.kt +++ b/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/service/RawDataWriter.kt @@ -21,6 +21,7 @@ import com.epam.drill.admin.writer.rawdata.views.MethodIgnoreRuleView interface RawDataWriter { suspend fun saveBuild(buildPayload: BuildPayload) + suspend fun saveBuildInfo(buildPayload: BuildPayload) suspend fun saveInstance(instancePayload: InstancePayload) suspend fun saveMethods(methodsPayload: MethodsPayload) suspend fun saveCoverage(coveragePayload: CoveragePayload) diff --git a/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/service/impl/RawDataServiceImpl.kt b/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/service/impl/RawDataServiceImpl.kt index 3231a4843..2cf4314d8 100644 --- a/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/service/impl/RawDataServiceImpl.kt +++ b/admin-writer/src/main/kotlin/com/epam/drill/admin/writer/rawdata/service/impl/RawDataServiceImpl.kt @@ -47,6 +47,26 @@ class RawDataServiceImpl( ) : RawDataWriter { override suspend fun saveBuild(buildPayload: BuildPayload) { + val build = Build( + id = generateBuildId( + buildPayload.groupId, + buildPayload.appId, + "", + buildPayload.commitSha, + buildPayload.buildVersion + ), + groupId = buildPayload.groupId, + appId = buildPayload.appId, + instanceId = null, + commitSha = buildPayload.commitSha, + buildVersion = buildPayload.buildVersion, + ) + transaction { + buildRepository.saveBuildId(build) + } + } + + override suspend fun saveBuildInfo(buildPayload: BuildPayload) { val build = Build( id = generateBuildId( buildPayload.groupId, @@ -66,7 +86,7 @@ class RawDataServiceImpl( commitAuthor = buildPayload.commitAuthor ) transaction { - buildRepository.create(build) + buildRepository.saveBuildInfo(build) } } @@ -94,12 +114,8 @@ class RawDataServiceImpl( instanceId = instancePayload.instanceId, commitSha = instancePayload.commitSha, buildVersion = instancePayload.buildVersion, - branch = null, - commitDate = null, - commitMessage = null, - commitAuthor = null ) - buildRepository.create(build) + buildRepository.saveBuildId(build) } instanceRepository.create(instance) } diff --git a/admin-writer/src/test/kotlin/com/epam/drill/admin/writer/rawdata/BuildsApiTest.kt b/admin-writer/src/test/kotlin/com/epam/drill/admin/writer/rawdata/BuildsApiTest.kt index 23f6c4d00..7260a0a54 100644 --- a/admin-writer/src/test/kotlin/com/epam/drill/admin/writer/rawdata/BuildsApiTest.kt +++ b/admin-writer/src/test/kotlin/com/epam/drill/admin/writer/rawdata/BuildsApiTest.kt @@ -16,6 +16,7 @@ package com.epam.drill.admin.writer.rawdata import com.epam.drill.admin.writer.rawdata.route.putBuilds +import com.epam.drill.admin.writer.rawdata.route.putBuildsInfo import com.epam.drill.admin.writer.rawdata.table.BuildTable import com.epam.drill.admin.test.* import com.epam.drill.admin.writer.rawdata.config.RawDataWriterDatabaseConfig @@ -28,6 +29,7 @@ import java.time.LocalDateTime import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull +import kotlin.test.assertNull import kotlin.test.assertTrue class BuildsApiTest : DatabaseTests({ RawDataWriterDatabaseConfig.init(it) }) { @@ -50,8 +52,56 @@ class BuildsApiTest : DatabaseTests({ RawDataWriterDatabaseConfig.init(it) }) { "groupId": "$testGroup", "appId": "$testApp", "buildVersion": "$testBuildVersion", + "commitSha": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" + } + """.trimIndent() + ) + }.apply { + assertEquals(HttpStatusCode.OK, status) + assertJsonEquals( + """ + { + "message": "Build saved" + } + """.trimIndent(), bodyAsText() + ) + } + + val savedBuilds = BuildTable.selectAll() + .filter { it[BuildTable.groupId] == testGroup } + .filter { it[BuildTable.appId] == testApp } + .filter { it[BuildTable.buildVersion] == testBuildVersion } + assertEquals(1, savedBuilds.size) + savedBuilds.forEach { + assertNull(it[BuildTable.branch]) + assertNotNull(it[BuildTable.commitSha]) + assertNull(it[BuildTable.commitAuthor]) + assertNull(it[BuildTable.commitMessage]) + assertNull(it[BuildTable.committedAt]) + assertTrue(it[BuildTable.createdAt] >= timeBeforeTest) + } + } + + @Test + fun `given new build, put builds info should create new build with info fields and return OK`() = withRollback { + val testGroup = "test-group" + val testApp = "test-app" + val testBuildVersion = "2.0.0" + val timeBeforeTest = LocalDateTime.now() + val app = drillApplication(rawDataServicesDIModule) { + putBuildsInfo() + } + + app.client.put("/builds/info") { + header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) + setBody( + """ + { + "groupId": "$testGroup", + "appId": "$testApp", + "buildVersion": "$testBuildVersion", + "commitSha": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "branch": "main", - "commitSha": "d3516472fd72cd0f9ccb7a1dc4b5e7b80a014fd2", "commitMessage": "Initial commit", "commitDate": "Thu Feb 27 10:06:24 2025 +0100", "commitAuthor": "John Doe" @@ -63,7 +113,7 @@ class BuildsApiTest : DatabaseTests({ RawDataWriterDatabaseConfig.init(it) }) { assertJsonEquals( """ { - "message": "Build saved" + "message": "Build info saved" } """.trimIndent(), bodyAsText() ) @@ -75,12 +125,69 @@ class BuildsApiTest : DatabaseTests({ RawDataWriterDatabaseConfig.init(it) }) { .filter { it[BuildTable.buildVersion] == testBuildVersion } assertEquals(1, savedBuilds.size) savedBuilds.forEach { - assertNotNull(it[BuildTable.branch]) + assertEquals("main", it[BuildTable.branch]) assertNotNull(it[BuildTable.commitSha]) + assertEquals("John Doe", it[BuildTable.commitAuthor]) + assertEquals("Initial commit", it[BuildTable.commitMessage]) + assertNotNull(it[BuildTable.committedAt]) + assertTrue(it[BuildTable.createdAt] >= timeBeforeTest) + } + } + + @Test + fun `given existing build info, put builds should not update info fields and return OK`() = withRollback { + val testGroup = "test-group" + val testApp = "test-app" + val testBuildVersion = "3.0.0" + val testCommitSha = "b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3" + val app = drillApplication(rawDataServicesDIModule) { + putBuildsInfo() + putBuilds() + } + app.client.put("/builds/info") { + header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) + setBody( + """ + { + "groupId": "$testGroup", + "appId": "$testApp", + "buildVersion": "$testBuildVersion", + "commitSha": "$testCommitSha", + "branch": "develop", + "commitMessage": "Feature commit", + "commitDate": "Thu Feb 27 10:06:24 2025 +0100", + "commitAuthor": "Jane Doe" + } + """.trimIndent() + ) + } + + app.client.put("/builds") { + header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) + setBody( + """ + { + "groupId": "$testGroup", + "appId": "$testApp", + "buildVersion": "$testBuildVersion", + "commitSha": "$testCommitSha" + } + """.trimIndent() + ) + }.apply { + assertEquals(HttpStatusCode.OK, status) + } + + val buildsBeforeInfo = BuildTable.selectAll() + .filter { it[BuildTable.groupId] == testGroup } + .filter { it[BuildTable.appId] == testApp } + .filter { it[BuildTable.buildVersion] == testBuildVersion } + assertEquals(1, buildsBeforeInfo.size) + buildsBeforeInfo.forEach { + assertNotNull(it[BuildTable.branch]) assertNotNull(it[BuildTable.commitAuthor]) assertNotNull(it[BuildTable.commitMessage]) assertNotNull(it[BuildTable.committedAt]) - assertTrue(it[BuildTable.createdAt] >= timeBeforeTest) } }