Skip to content

More E2E entity creation flows that trigger jobs #581

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jun 4, 2025

Conversation

j7m4
Copy link
Collaborator

@j7m4 j7m4 commented Jun 3, 2025

Summary by CodeRabbit

  • New Features

    • Enhanced job flow tests to cover multiple job queueing scenarios with new helper functions and assertions.
    • Expanded YAML test scenarios to support tier- and kind-based resource and environment selection.
  • Bug Fixes

    • Updated policy YAML structure to ensure compatibility with expected data formats.
  • Refactor

    • Unified entity tracking and references in tests for improved consistency and maintainability.
    • Replaced creation methods with upsert methods for more robust test setup and teardown.
    • Improved request/response tracking for API interactions in test utilities.
  • Documentation

    • Updated usage examples in documentation to reflect new function signatures and parameters.

Copy link
Contributor

coderabbitai bot commented Jun 3, 2025

Walkthrough

This update refactors the end-to-end testing infrastructure by replacing the EntitiesCache interface with a new EntityRefs class for managing entity references. All entity creation methods in EntitiesBuilder are renamed and now return detailed request/response information. Test suites are updated to use refs instead of cache throughout.

Changes

File(s) Change Summary
e2e/api/entities-builder.ts Refactored to use EntityRefs instead of EntitiesCache; entity creation methods renamed to upsert*, now return detailed request/response info; cleanup returns operation results.
e2e/api/entity-refs.ts Introduced new file defining EntityRefs class and supporting interfaces/classes for entity reference management and querying.
e2e/tests/api/deployment-remove-event.spec.ts
e2e/tests/api/deployment-variable.spec.ts
e2e/tests/api/deployment-version.spec.ts
e2e/tests/api/deployments.spec.ts
e2e/tests/api/environments.spec.ts
e2e/tests/api/job-agents.spec.ts
e2e/tests/api/jobs/job-flow-1.spec.ts
e2e/tests/api/policies/matched-release-targets.spec.ts
e2e/tests/api/release-targets.spec.ts
e2e/tests/api/release.spec.ts
e2e/tests/api/resource-grouping.spec.ts
e2e/tests/api/resource-relationships.spec.ts
e2e/tests/api/resource-variables.spec.ts
e2e/tests/api/resources.spec.ts
e2e/tests/api/yaml-import.spec.ts
Updated all tests to use builder.refs instead of builder.cache; switched to upsert* methods for entity setup; updated cleanup logic.
e2e/tests/api/jobs/job-flow-1.spec.ts Added new tests for job queueing scenarios; introduced polling helper and Job interface; restructured setup/teardown.
e2e/tests/api/jobs/job-flow-entities.spec.yaml Expanded YAML to define tier/kind-based metadata/selectors for resources, deployments, and environments.
e2e/tests/api/yaml-import.spec.yaml Modified a policy field from a mapping to a list of mappings for versionAnyApprovals.
e2e/README.md Updated usage example for importEntitiesFromYaml to include a tracer argument.

Sequence Diagram(s)

sequenceDiagram
    participant TestSuite
    participant EntitiesBuilder
    participant EntityRefs
    participant ApiClient

    TestSuite->>EntitiesBuilder: upsertSystem()
    EntitiesBuilder->>ApiClient: Create/Update System
    ApiClient-->>EntitiesBuilder: Response
    EntitiesBuilder->>EntityRefs: Store system reference

    TestSuite->>EntitiesBuilder: upsertResources()
    EntitiesBuilder->>ApiClient: Create/Update Resources
    ApiClient-->>EntitiesBuilder: Response(s)
    EntitiesBuilder->>EntityRefs: Store resource references

    TestSuite->>EntitiesBuilder: upsertDeployments()
    EntitiesBuilder->>ApiClient: Create/Update Deployments
    ApiClient-->>EntitiesBuilder: Response(s)
    EntitiesBuilder->>EntityRefs: Store deployment references

    TestSuite->>EntitiesBuilder: cleanupImportedEntities()
    EntitiesBuilder->>ApiClient: Delete Entities
    ApiClient-->>EntitiesBuilder: Delete Responses
    EntitiesBuilder-->>TestSuite: Array of FetchResultInfo
Loading

Possibly related PRs

  • ctrlplanedev/ctrlplane#576: Introduced the original EntitiesBuilder class, which is now refactored in this PR to use EntityRefs and return detailed request/response info.

Suggested reviewers

  • jsbroks

Poem

In the warren of tests, a refactor appears,
Old caches replaced, new entity peers!
With upserts and refs, the code hops anew,
Each request tracked, responses in view.
🐇 From YAML to jobs, the rabbit approves—
Hopping through changes, with elegant moves!


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63188bf and 5ead6c7.

📒 Files selected for processing (1)
  • e2e/tests/api/deployment-variable.spec.ts (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e/tests/api/deployment-variable.spec.ts
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

j7m4 added 4 commits June 3, 2025 12:28
# Conflicts:
#	e2e/api/entities-builder.ts
#	e2e/api/entity-fixtures.ts
#	e2e/api/schema.ts
#	e2e/tests/api/deployment-remove-event.spec.ts
#	e2e/tests/api/deployment-variable.spec.ts
#	e2e/tests/api/deployment-version.spec.ts
#	e2e/tests/api/deployments.spec.ts
#	e2e/tests/api/environments.spec.ts
#	e2e/tests/api/job-agents.spec.ts
#	e2e/tests/api/jobs/job-flow-1.spec.ts
#	e2e/tests/api/jobs/job-flow-entities.spec.yaml
#	e2e/tests/api/policies/matched-release-targets.spec.ts
#	e2e/tests/api/release-targets.spec.ts
#	e2e/tests/api/release.spec.ts
#	e2e/tests/api/resource-grouping.spec.ts
#	e2e/tests/api/resource-relationships.spec.ts
#	e2e/tests/api/resource-variables.spec.ts
#	e2e/tests/api/resources.spec.ts
#	e2e/tests/api/yaml-import.spec.ts
@j7m4 j7m4 marked this pull request as ready for review June 3, 2025 18:22
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🔭 Outside diff range comments (3)
e2e/tests/api/policies/matched-release-targets.spec.ts (3)

452-452: ⚠️ Potential issue

Fix undefined variable reference.

The variable importedEntities is not defined. This should use builder.refs consistent with the refactor.

Apply this diff:

-    const systemPrefix = importedEntities.system.slug.split("-")[0]!;
+    const systemPrefix = builder.refs.system.slug.split("-")[0]!;

510-510: ⚠️ Potential issue

Fix undefined variable references throughout the file.

Multiple references to undefined importedEntities variable need to be updated to use builder.refs to be consistent with the refactor.

Apply this diff pattern throughout:

-    const systemPrefix = importedEntities.system.slug.split("-")[0]!;
+    const systemPrefix = builder.refs.system.slug.split("-")[0]!;

Also applies to: 586-586, 664-664, 747-747, 853-853, 936-936, 1042-1042, 1125-1125, 1231-1231, 1359-1359


593-593: ⚠️ Potential issue

Fix system ID reference.

The reference to importedEntities.system.id should use builder.refs.system.id to be consistent with the refactor.

Apply this diff:

-        systemId: importedEntities.system.id,
+        systemId: builder.refs.system.id,
🧹 Nitpick comments (3)
e2e/tests/api/deployment-variable.spec.ts (1)

216-216: Fix typo in test name.

There's a typo in the test name: "shoudl" should be "should".

-  test("shoudl fail if more than one default value is provided", async ({ api }) => {
+  test("should fail if more than one default value is provided", async ({ api }) => {
e2e/api/entity-refs.ts (1)

164-169: Consider adding the item details to the error message.

The error message could be more helpful by including information about what was being searched for.

 function exactlyOne<T>(items: Array<T>): T {
   if (items.length !== 1) {
-    throw new Error(`Expected exactly one item, but found ${items.length}.`);
+    throw new Error(`Expected exactly one item, but found ${items.length} items.`);
   }
   return items[0]!;
 }

Additionally, consider updating the calling methods to provide context:

public getEnvironmentLike(match: string): EnvironmentRef {
  const filtered = this.environments.filter((env) => matches(env.name, match));
  if (filtered.length !== 1) {
    throw new Error(`Expected exactly one environment matching "${match}", but found ${filtered.length}.`);
  }
  return filtered[0];
}
e2e/api/entities-builder.ts (1)

9-12: Consider improving type safety for the interface.

The use of any types in FetchResponse<any, any, any> reduces type safety. Consider using generic type parameters to maintain type information from the API calls.

-export interface FetchResultInfo {
-  fetchResponse: FetchResponse<any, any, any>;
-  requestBody?: any;
-}
+export interface FetchResultInfo<TData = any, TError = any, TBody = any> {
+  fetchResponse: FetchResponse<TData, TError, TBody>;
+  requestBody?: TBody;
+}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2de5f35 and 63188bf.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • e2e/README.md (1 hunks)
  • e2e/api/entities-builder.ts (1 hunks)
  • e2e/api/entity-refs.ts (1 hunks)
  • e2e/package.json (0 hunks)
  • e2e/tests/api/deployment-remove-event.spec.ts (5 hunks)
  • e2e/tests/api/deployment-variable.spec.ts (9 hunks)
  • e2e/tests/api/deployment-version.spec.ts (8 hunks)
  • e2e/tests/api/deployments.spec.ts (16 hunks)
  • e2e/tests/api/environments.spec.ts (15 hunks)
  • e2e/tests/api/job-agents.spec.ts (1 hunks)
  • e2e/tests/api/jobs/job-flow-1.spec.ts (1 hunks)
  • e2e/tests/api/jobs/job-flow-entities.spec.yaml (1 hunks)
  • e2e/tests/api/policies/matched-release-targets.spec.ts (7 hunks)
  • e2e/tests/api/release-targets.spec.ts (3 hunks)
  • e2e/tests/api/release.spec.ts (11 hunks)
  • e2e/tests/api/resource-grouping.spec.ts (1 hunks)
  • e2e/tests/api/resource-relationships.spec.ts (1 hunks)
  • e2e/tests/api/resource-variables.spec.ts (10 hunks)
  • e2e/tests/api/resources.spec.ts (10 hunks)
  • e2e/tests/api/yaml-import.spec.ts (4 hunks)
  • e2e/tests/api/yaml-import.spec.yaml (1 hunks)
💤 Files with no reviewable changes (1)
  • e2e/package.json
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • e2e/tests/api/release-targets.spec.ts
  • e2e/tests/api/job-agents.spec.ts
  • e2e/tests/api/resource-grouping.spec.ts
  • e2e/tests/api/deployment-variable.spec.ts
  • e2e/tests/api/resource-relationships.spec.ts
  • e2e/tests/api/deployment-remove-event.spec.ts
  • e2e/tests/api/deployment-version.spec.ts
  • e2e/tests/api/yaml-import.spec.ts
  • e2e/tests/api/deployments.spec.ts
  • e2e/tests/api/environments.spec.ts
  • e2e/tests/api/policies/matched-release-targets.spec.ts
  • e2e/tests/api/release.spec.ts
  • e2e/tests/api/resource-variables.spec.ts
  • e2e/tests/api/resources.spec.ts
  • e2e/tests/api/jobs/job-flow-1.spec.ts
  • e2e/api/entities-builder.ts
  • e2e/api/entity-refs.ts
🧬 Code Graph Analysis (1)
e2e/tests/api/resource-variables.spec.ts (3)
e2e/tests/fixtures.ts (1)
  • test (11-26)
e2e/api/entities-builder.ts (1)
  • cleanupImportedEntities (340-389)
packages/db/src/schema/workspace.ts (1)
  • workspace (18-27)
🔇 Additional comments (34)
e2e/tests/api/resource-grouping.spec.ts (1)

13-14: LGTM! Method naming improved to reflect upsert semantics.

The change from createSystem() and createResources() to upsertSystem() and upsertResources() aligns with the refactor objectives and provides better semantics for idempotent operations that can handle both creation and updates.

e2e/README.md (1)

58-63:

❓ Verification inconclusive

Documentation updated to reflect new function signature.

The addition of the tracer parameter aligns with the enhanced tracking capabilities introduced in this refactor.

Verify that all existing usages of importEntitiesFromYaml have been updated to include the new tracer parameter:


🏁 Script executed:

#!/bin/bash
# Description: Check for any remaining usages of importEntitiesFromYaml with the old 3-parameter signature
# Expected: All calls should include the tracer parameter

rg -A 3 "importEntitiesFromYaml\(" --type ts

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Search for any calls to importEntitiesFromYaml in TS/JS/TSX files to ensure the tracer parameter is included
rg -n "importEntitiesFromYaml\(" --glob '*.ts' --glob '*.js' --glob '*.tsx' -C2

Length of output: 81


Manual verification required: confirm all importEntitiesFromYaml calls include the new tracer parameter

The previous search returned no matches, which may be due to the function only being invoked in non-.ts/.js files or multi-line contexts. Please double-check manually that every usage—regardless of file extension or call style—has been updated to pass the new tracer argument. For example, you can run:

# Broad search across all files and extensions
rg -n "importEntitiesFromYaml" -C2

Ensure that any invocation you find follows the updated four-parameter signature:

await importEntitiesFromYaml(api, workspace.id, yamlPath, tracer);
e2e/tests/api/deployment-remove-event.spec.ts (3)

15-15: LGTM! Consistent method rename to upsert semantics.

The change from createSystem() to upsertSystem() is consistent with the refactor pattern seen across other test files.


19-19: LGTM! Cleanup updated to use new EntityRefs class.

The change from builder.cache to builder.refs aligns with the refactor to use the new EntityRefs class for entity management.


27-27: LGTM! Consistent entity reference updates across all test methods.

All system entity references have been consistently updated from builder.cache.system to builder.refs.system, maintaining uniformity across the test suite.

Also applies to: 113-113, 201-201, 290-290, 376-376

e2e/tests/api/release-targets.spec.ts (3)

14-17: LGTM! Consistent refactoring to upsert methods.

The changes from create* to upsert* methods align with the broader refactoring to use EntityRefs and provide enhanced entity tracking capabilities.


21-21: LGTM! Updated cleanup to use new EntityRefs.

The cleanup function signature correctly updated to use builder.refs instead of builder.cache, consistent with the EntityRefs refactoring.


30-30: LGTM! Consistent entity access via refs.

All entity access patterns consistently updated from builder.cache to builder.refs, maintaining the same functionality while using the new EntityRefs interface.

Also applies to: 66-72

e2e/tests/api/job-agents.spec.ts (2)

15-17: LGTM! Consistent refactoring to upsert methods.

The setup methods correctly updated to use the new upsert* variants, aligning with the EntityRefs refactoring.


21-21: LGTM! Updated cleanup to use EntityRefs.

Cleanup function correctly updated to use builder.refs, consistent with the new entity management approach.

e2e/tests/api/deployment-variable.spec.ts (3)

15-16: LGTM! Consistent setup method refactoring.

Setup methods correctly updated to use upsert* variants, aligning with the EntityRefs refactoring.


21-21: LGTM! Updated cleanup to use EntityRefs.

Cleanup correctly updated to use builder.refs instead of builder.cache.


25-25: LGTM! Consistent entity access via refs.

All deployment entity access patterns consistently updated from builder.cache.deployments[0] to builder.refs.deployments[0], maintaining functionality while using the new EntityRefs interface.

Also applies to: 72-72, 150-150, 217-217, 261-261, 390-390, 496-496, 627-627, 714-714

e2e/tests/api/resource-variables.spec.ts (4)

15-17: LGTM! Consistent setup method refactoring.

Setup methods correctly updated to use upsert* variants, aligning with the EntityRefs refactoring.


21-21: LGTM! Updated cleanup to use EntityRefs.

Cleanup correctly updated to use builder.refs instead of builder.cache.


25-25: LGTM! Consistent system entity access via refs.

All system slug access patterns consistently updated from builder.cache.system.slug to builder.refs.system.slug, maintaining functionality while using the new EntityRefs interface.

Also applies to: 80-80, 141-141, 174-174, 269-269, 421-421, 596-596


326-326: LGTM! Consistent deployment entity access via refs.

All deployment entity access patterns consistently updated from builder.cache.deployments[0] to builder.refs.deployments[0], maintaining functionality while using the new EntityRefs interface.

Also applies to: 481-481, 655-655

e2e/tests/api/resource-relationships.spec.ts (1)

16-16: LGTM! Clean refactoring from EntitiesCache to EntityRefs.

The changes consistently update entity references from builder.cache to builder.refs and method calls from create* to upsert* semantics. This aligns well with the broader refactoring described in the PR objectives.

Also applies to: 18-19, 23-23

e2e/tests/api/deployment-version.spec.ts (2)

15-16: LGTM! Consistent refactoring to new entity management system.

The setup and cleanup methods have been properly updated to use the new upsert* methods and EntityRefs interface.

Also applies to: 20-20


33-33: LGTM! All entity references consistently updated.

All deployment ID references have been properly migrated from builder.cache.deployments[0].id to builder.refs.deployments[0].id.

Also applies to: 46-46, 61-61, 84-84, 107-107, 154-154, 195-195

e2e/tests/api/yaml-import.spec.ts (3)

4-4: LGTM! Import statement properly simplified.

The import has been correctly updated to use the unified API export.


14-19: Verify the createDeploymentVariables method.

Most entity creation methods have been updated to use upsert* semantics, but createDeploymentVariables() remains unchanged. Please verify if this is intentional (e.g., no upsertDeploymentVariables method exists) or if this should also be updated.


27-27: LGTM! All entity references consistently migrated.

All entity access points have been properly updated from builder.cache to builder.refs throughout the test cases.

Also applies to: 33-33, 69-69, 72-72, 88-88, 90-90

e2e/tests/api/deployments.spec.ts (2)

15-17: LGTM! Setup and cleanup properly refactored.

The test setup has been correctly updated to use upsert* methods and the new EntityRefs interface for cleanup.

Also applies to: 21-21


30-30: LGTM! Comprehensive entity reference migration.

All entity references throughout the test cases have been systematically updated from builder.cache to builder.refs. The changes maintain consistency across system IDs, environment lists, resource lists, and system slugs.

Also applies to: 46-46, 74-74, 108-108, 115-115, 201-201, 207-207, 251-251, 265-265, 275-275, 281-281, 354-355, 369-369, 380-380, 409-409, 456-456, 462-462, 508-508, 536-536

e2e/tests/api/resources.spec.ts (3)

15-17: LGTM! Entity creation methods properly updated.

The refactor from createX() to upsertX() methods is correctly applied and aligns with the new EntityRefs pattern.


21-21: LGTM! Cleanup method properly updated.

The change from builder.cache to builder.refs is correctly applied for the cleanup function.


25-25: LGTM! Entity references consistently updated.

All entity references have been correctly updated from builder.cache to builder.refs throughout the test suite. The refactor is consistently applied.

Also applies to: 52-53, 103-103, 137-137, 166-166, 215-216, 238-238, 285-285, 339-340, 368-368

e2e/tests/api/environments.spec.ts (2)

15-17: LGTM! Entity setup methods properly refactored.

The transition from createX() to upsertX() methods is correctly implemented and consistent with the new entity management pattern.


21-21: LGTM! Entity references consistently updated throughout.

All references to entities have been properly updated from the old builder.cache pattern to the new builder.refs pattern. The refactor is thoroughly and consistently applied.

Also applies to: 29-29, 43-43, 82-82, 96-96, 112-112, 149-149, 157-157, 198-199, 213-213, 224-224, 228-228, 260-260, 290-290, 310-310, 334-334, 341-341, 430-430, 457-457

e2e/tests/api/release.spec.ts (2)

15-17: LGTM! Entity setup methods correctly updated.

The refactor from createX() to upsertX() methods is properly implemented and aligns with the new entity management approach.


22-22: LGTM! Entity references consistently refactored.

All entity references have been correctly updated from builder.cache to builder.refs throughout the test suite. The refactor is comprehensively applied with no missed references.

Also applies to: 30-30, 36-36, 53-53, 113-113, 119-119, 166-166, 230-230, 236-236, 270-270, 334-334, 340-340, 471-471, 477-477, 589-589, 595-595, 718-718, 724-724, 847-847, 853-853

e2e/tests/api/policies/matched-release-targets.spec.ts (1)

35-35: LGTM! These entity references are correctly updated.

These references properly use builder.refs and are consistent with the refactor pattern.

Also applies to: 93-93, 169-169, 241-241, 299-299, 375-375, 381-381

e2e/api/entity-refs.ts (1)

175-185: LGTM! Clean implementation of Fisher-Yates shuffle.

The takeRandom function correctly implements the Fisher-Yates shuffle algorithm with proper bounds checking and uses modern array destructuring for swapping.

@@ -109,4 +109,4 @@ policies:
key: tier
value: production
versionAnyApprovals:
requiredApprovalsCount: 2
- requiredApprovalsCount: 2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify the data structure change for versionAnyApprovals.

The change from a single object to an array containing one object suggests a schema evolution to support multiple approval configurations. Please confirm this aligns with the expected API schema and doesn't break existing functionality.

Verify the policy API schema accepts the new array structure:


🏁 Script executed:

#!/bin/bash
# Description: Check how versionAnyApprovals is used in policy-related code
# Expected: Code should handle array structure for versionAnyApprovals

rg -A 5 -B 5 "versionAnyApprovals" --type ts

Length of output: 45059


versionAnyApprovals remains a single object – array in YAML spec will break schema

Our investigation shows that throughout the codebase versionAnyApprovals is defined and persisted as a single nullable object, not an array:

  • In packages/db/src/schema/policy-relations.ts:
    versionAnyApprovals: one(policyRuleAnyApproval)
  • In packages/rule-engine/src/types.ts:
    versionAnyApprovals: schema.PolicyRuleAnyApproval | null
  • In the policy‐creation and update routines, and in API router queries, it’s always treated as one object.

Changing your E2E YAML to use an array will not match the runtime model and will break import and validation. You should either:

  • Revert the YAML spec back to:

    versionAnyApprovals:
      requiredApprovalsCount: 2

    or

  • Fully migrate versionAnyApprovals to a list by updating:
    • packages/db/src/schema/policy-relations.ts (use many(...))
    • packages/db/src/schema/policy.ts, types in rule-engine, manager functions, create/update handlers
    • API router return types and OpenAPI specs
    • SDK and E2E schemas

Please choose one path to keep the test and implementation in sync.

🤖 Prompt for AI Agents
In e2e/tests/api/yaml-import.spec.yaml at line 112, the versionAnyApprovals
field was changed from a single object to an array, which conflicts with the
current API schema that expects a single nullable object. To fix this, revert
the YAML back to using a single object for versionAnyApprovals with
requiredApprovalsCount: 2, or alternatively, if you want to support multiple
approvals, update the entire codebase schema, types, handlers, API specs, and
SDK to handle an array consistently. Choose one approach to keep the test and
implementation aligned.

Comment on lines +17 to +20
builder.upsertSystem();
builder.upsertResources();
builder.upsertEnvironments();
builder.upsertDeployments();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing await keywords for async operations.

The entity creation methods are async and need to be awaited to ensure proper setup before tests run.

Apply this diff to fix the missing await keywords:

-    builder.upsertSystem();
-    builder.upsertResources();
-    builder.upsertEnvironments();
-    builder.upsertDeployments();
+    await builder.upsertSystem();
+    await builder.upsertResources();
+    await builder.upsertEnvironments();
+    await builder.upsertDeployments();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
builder.upsertSystem();
builder.upsertResources();
builder.upsertEnvironments();
builder.upsertDeployments();
await builder.upsertSystem();
await builder.upsertResources();
await builder.upsertEnvironments();
await builder.upsertDeployments();
🤖 Prompt for AI Agents
In e2e/tests/api/policies/matched-release-targets.spec.ts around lines 17 to 20,
the calls to builder.upsertSystem(), builder.upsertResources(),
builder.upsertEnvironments(), and builder.upsertDeployments() are asynchronous
but missing the await keyword. Add await before each of these method calls to
ensure they complete before proceeding, guaranteeing proper test setup.

Comment on lines +57 to +59
- name: "{{ prefix }}-qa-database-resource"
<<: *kindService
identifier: "{{ prefix }}-qa-database-resource"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect kind anchors for database resources.

The database resources are using *kindService anchor but should use *kindDatabase anchor to match their names and intended purpose.

Apply this diff to fix the incorrect anchor references:

  - name: "{{ prefix }}-qa-database-resource"
-    <<: *kindService
+    <<: *kindDatabase
    identifier: "{{ prefix }}-qa-database-resource"
  - name: "{{ prefix }}-dev-database-resource"
-    <<: *kindService
+    <<: *kindDatabase
    identifier: "{{ prefix }}-dev-database-resource"

Also applies to: 65-67

🤖 Prompt for AI Agents
In e2e/tests/api/jobs/job-flow-entities.spec.yaml at lines 57-59 and 65-67, the
database resource entries incorrectly use the *kindService anchor. Replace the
*kindService anchor with *kindDatabase for these database resource definitions
to correctly reflect their kind and purpose.

Comment on lines +104 to +142
async function nextJobs(
api: Client<paths, `${string}/${string}`>,
agentId: string,
expectedJobCount: number,
): Promise<Job[]> {
let jobs: Job[] = [];
let attempts = 0;

while (jobs.length < expectedJobCount && attempts < 10) {
await new Promise((resolve) => setTimeout(resolve, 1000));
const fetchResponse = await api.GET("/v1/job-agents/{agentId}/queue/next", {
params: {
path: {
agentId,
},
},
});

expect(fetchResponse.response.ok).toBe(true);

const nextJobs = fetchResponse.data;
expect(nextJobs?.jobs).toBeDefined();

expect(Array.isArray(nextJobs?.jobs)).toBe(true);
jobs.push(...((nextJobs?.jobs as Job[]) || []));
}

expect(
jobs.every((job) => job.jobAgentId === agentId),
"expected agentId",
).toBe(true);
expect(
_.uniq(jobs.map((job) => job.id)).length == jobs.length,
"expected unique jobIds",
).toBe(true);

expect(jobs.length, "job count").toBe(expectedJobCount);
return jobs;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider improving the polling logic for better reliability.

The current implementation has a few areas that could be improved:

  1. The timeout (10 seconds) might be insufficient under heavy load
  2. The function continues polling even after getting the expected number of jobs
  3. No clear error message when timeout is reached

Consider this improved implementation:

 async function nextJobs(
   api: Client<paths, `${string}/${string}`>,
   agentId: string,
   expectedJobCount: number,
+  maxAttempts: number = 10,
+  delayMs: number = 1000,
 ): Promise<Job[]> {
   let jobs: Job[] = [];
   let attempts = 0;
 
-  while (jobs.length < expectedJobCount && attempts < 10) {
-    await new Promise((resolve) => setTimeout(resolve, 1000));
+  while (jobs.length < expectedJobCount && attempts < maxAttempts) {
+    if (attempts > 0) {
+      await new Promise((resolve) => setTimeout(resolve, delayMs));
+    }
+    attempts++;
+
     const fetchResponse = await api.GET("/v1/job-agents/{agentId}/queue/next", {
       params: {
         path: {
           agentId,
         },
       },
     });
 
     expect(fetchResponse.response.ok).toBe(true);
 
     const nextJobs = fetchResponse.data;
     expect(nextJobs?.jobs).toBeDefined();
 
     expect(Array.isArray(nextJobs?.jobs)).toBe(true);
     jobs.push(...((nextJobs?.jobs as Job[]) || []));
+
+    // Early exit if we have enough jobs
+    if (jobs.length >= expectedJobCount) {
+      break;
+    }
   }
 
+  if (jobs.length < expectedJobCount) {
+    throw new Error(`Timeout: Expected ${expectedJobCount} jobs but got ${jobs.length} after ${attempts} attempts`);
+  }
+
   expect(
     jobs.every((job) => job.jobAgentId === agentId),
     "expected agentId",
   ).toBe(true);
   expect(
     _.uniq(jobs.map((job) => job.id)).length == jobs.length,
     "expected unique jobIds",
   ).toBe(true);
 
   expect(jobs.length, "job count").toBe(expectedJobCount);
   return jobs;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function nextJobs(
api: Client<paths, `${string}/${string}`>,
agentId: string,
expectedJobCount: number,
): Promise<Job[]> {
let jobs: Job[] = [];
let attempts = 0;
while (jobs.length < expectedJobCount && attempts < 10) {
await new Promise((resolve) => setTimeout(resolve, 1000));
const fetchResponse = await api.GET("/v1/job-agents/{agentId}/queue/next", {
params: {
path: {
agentId,
},
},
});
expect(fetchResponse.response.ok).toBe(true);
const nextJobs = fetchResponse.data;
expect(nextJobs?.jobs).toBeDefined();
expect(Array.isArray(nextJobs?.jobs)).toBe(true);
jobs.push(...((nextJobs?.jobs as Job[]) || []));
}
expect(
jobs.every((job) => job.jobAgentId === agentId),
"expected agentId",
).toBe(true);
expect(
_.uniq(jobs.map((job) => job.id)).length == jobs.length,
"expected unique jobIds",
).toBe(true);
expect(jobs.length, "job count").toBe(expectedJobCount);
return jobs;
}
async function nextJobs(
api: Client<paths, `${string}/${string}`>,
agentId: string,
expectedJobCount: number,
maxAttempts: number = 10,
delayMs: number = 1000,
): Promise<Job[]> {
let jobs: Job[] = [];
let attempts = 0;
while (jobs.length < expectedJobCount && attempts < maxAttempts) {
if (attempts > 0) {
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
attempts++;
const fetchResponse = await api.GET("/v1/job-agents/{agentId}/queue/next", {
params: {
path: {
agentId,
},
},
});
expect(fetchResponse.response.ok).toBe(true);
const nextJobs = fetchResponse.data;
expect(nextJobs?.jobs).toBeDefined();
expect(Array.isArray(nextJobs?.jobs)).toBe(true);
jobs.push(...((nextJobs?.jobs as Job[]) || []));
// Early exit if we have enough jobs
if (jobs.length >= expectedJobCount) {
break;
}
}
if (jobs.length < expectedJobCount) {
throw new Error(
`Timeout: Expected ${expectedJobCount} jobs but got ${jobs.length} after ${attempts} attempts`
);
}
expect(
jobs.every((job) => job.jobAgentId === agentId),
"expected agentId",
).toBe(true);
expect(
_.uniq(jobs.map((job) => job.id)).length == jobs.length,
"expected unique jobIds",
).toBe(true);
expect(jobs.length, "job count").toBe(expectedJobCount);
return jobs;
}
🤖 Prompt for AI Agents
In e2e/tests/api/jobs/job-flow-1.spec.ts around lines 104 to 142, improve the
polling logic by adding a break condition to stop polling once the expected
number of jobs is reached, extend the timeout duration to handle heavy load
scenarios more reliably, and throw a clear error message if the timeout is
reached without fetching enough jobs. This will make the function more efficient
and provide better feedback on failure.

Comment on lines +136 to +141
public onePolicy(): PolicyRef {
if (this.policies.length === 0) {
throw new Error("No policies available.");
}
return this.policies[0];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make error handling consistent across all one* methods.

The onePolicy method explicitly checks for empty array and throws an error, while other one* methods rely on takeRandom to throw. This inconsistency could be confusing.

Either remove the explicit check in onePolicy to match other methods:

  public onePolicy(): PolicyRef {
-    if (this.policies.length === 0) {
-      throw new Error("No policies available.");
-    }
-    return this.policies[0];
+    return takeRandom(this.policies, 1)[0];
  }

Or add similar checks to all one* methods for consistency. The current implementation would work fine with just takeRandom since it already handles the empty array case.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public onePolicy(): PolicyRef {
if (this.policies.length === 0) {
throw new Error("No policies available.");
}
return this.policies[0];
}
public onePolicy(): PolicyRef {
return takeRandom(this.policies, 1)[0];
}
🤖 Prompt for AI Agents
In e2e/api/entity-refs.ts around lines 136 to 141, the onePolicy method
explicitly checks for an empty policies array and throws an error, while other
one* methods rely on the takeRandom function to handle empty arrays. To maintain
consistency, remove the explicit empty check and error throw in onePolicy and
instead return the result of takeRandom(this.policies), allowing takeRandom to
handle empty arrays uniformly across all one* methods.

Comment on lines +166 to 168
const fetchResponse = await this.api.POST(uriPath, {
body: requestBody,
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add response status validation for consistency.

 const fetchResponse = await this.api.POST(uriPath, {
   body: requestBody,
 });

+if (fetchResponse.response.status !== 201) {
+  throw new Error(
+    `Failed to create deployment: ${JSON.stringify(fetchResponse.error)}`,
+  );
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fetchResponse = await this.api.POST(uriPath, {
body: requestBody,
});
const fetchResponse = await this.api.POST(uriPath, {
body: requestBody,
});
if (fetchResponse.response.status !== 201) {
throw new Error(
`Failed to create deployment: ${JSON.stringify(fetchResponse.error)}`,
);
}
🤖 Prompt for AI Agents
In e2e/api/entities-builder.ts around lines 166 to 168, the POST request
response is not validated for its status. Add a check after the POST call to
verify that the response status indicates success (e.g., status code 200 or in
the 2xx range). If the status is not successful, handle the error appropriately,
such as throwing an error or logging a failure, to ensure consistent response
validation.

Comment on lines +246 to 254
const fetchResponse = await this.api.POST(
"/v1/deployments/{deploymentId}/variables",
{
params: { path: { deploymentId: deploymentResult.id } },
body: {
...variable,
directValues: variable.directValues?.map((dv) => ({
...dv,
resourceSelector: dv.resourceSelector ?? null,
})),
referenceValues: variable.referenceValues?.map((rv) => ({
...rv,
resourceSelector: rv.resourceSelector ?? null,
})),
requestBody,
},
},
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect request body wrapping.

The request body is incorrectly wrapped in an extra object layer. The requestBody variable already contains the body data.

 const fetchResponse = await this.api.POST(
   "/v1/deployments/{deploymentId}/variables",
   {
     params: { path: { deploymentId: deploymentResult.id } },
-    body: {
-      requestBody,
-    },
+    body: requestBody,
   },
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fetchResponse = await this.api.POST(
"/v1/deployments/{deploymentId}/variables",
{
params: { path: { deploymentId: deploymentResult.id } },
body: {
...variable,
directValues: variable.directValues?.map((dv) => ({
...dv,
resourceSelector: dv.resourceSelector ?? null,
})),
referenceValues: variable.referenceValues?.map((rv) => ({
...rv,
resourceSelector: rv.resourceSelector ?? null,
})),
requestBody,
},
},
);
const fetchResponse = await this.api.POST(
"/v1/deployments/{deploymentId}/variables",
{
params: { path: { deploymentId: deploymentResult.id } },
- body: {
- requestBody,
- },
+ body: requestBody,
},
);
🤖 Prompt for AI Agents
In e2e/api/entities-builder.ts around lines 246 to 254, the request body is
incorrectly wrapped inside an extra object layer by using { requestBody }.
Remove this wrapping and pass requestBody directly as the body in the POST
request to ensure the correct data structure is sent.

Comment on lines +135 to 138
const fetchResponse = await this.api.POST("/v1/environments", {
body: requestBody,
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add response status validation.

For consistency with upsertSystem and to ensure proper error handling, validate the response status.

 const fetchResponse = await this.api.POST("/v1/environments", {
   body: requestBody,
 });

+if (fetchResponse.response.status !== 201) {
+  throw new Error(
+    `Failed to create environment: ${JSON.stringify(fetchResponse.error)}`,
+  );
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fetchResponse = await this.api.POST("/v1/environments", {
body: requestBody,
});
const fetchResponse = await this.api.POST("/v1/environments", {
body: requestBody,
});
if (fetchResponse.response.status !== 201) {
throw new Error(
`Failed to create environment: ${JSON.stringify(fetchResponse.error)}`,
);
}
🤖 Prompt for AI Agents
In e2e/api/entities-builder.ts around lines 135 to 138, the POST request to
"/v1/environments" lacks validation of the response status. Add a check after
the POST call to verify that the response status indicates success (e.g., status
code 200 or 201). If the status is not successful, throw an error or handle it
appropriately to ensure consistent error handling like in the upsertSystem
method.

Comment on lines +88 to 96
const fetchResponse = await this.api.POST("/v1/resources", {
body: requestBody,
});

if (resourceResponse.response.status !== 200) {
throw new Error(
`Failed to create resource: ${JSON.stringify(
resourceResponse.error,
)}`,
);
}
results.push({
fetchResponse,
requestBody,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add response status validation.

The method doesn't validate the response status, which could hide API failures.

 const fetchResponse = await this.api.POST("/v1/resources", {
   body: requestBody,
 });

+if (fetchResponse.response.status !== 201) {
+  throw new Error(
+    `Failed to create resource: ${JSON.stringify(fetchResponse.error)}`,
+  );
+}

 results.push({
   fetchResponse,
   requestBody,
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fetchResponse = await this.api.POST("/v1/resources", {
body: requestBody,
});
if (resourceResponse.response.status !== 200) {
throw new Error(
`Failed to create resource: ${JSON.stringify(
resourceResponse.error,
)}`,
);
}
results.push({
fetchResponse,
requestBody,
});
}
const fetchResponse = await this.api.POST("/v1/resources", {
body: requestBody,
});
if (fetchResponse.response.status !== 201) {
throw new Error(
`Failed to create resource: ${JSON.stringify(fetchResponse.error)}`,
);
}
results.push({
fetchResponse,
requestBody,
});
}
🤖 Prompt for AI Agents
In e2e/api/entities-builder.ts between lines 88 and 96, the code pushes the API
response to results without checking if the response status indicates success.
Add validation after the POST request to check the response status code, and
handle or throw an error if the status indicates failure, ensuring API failures
are detected and not silently ignored.

Comment on lines 99 to 104
for (const r of this.fixtures.resources) {
this.cache.resources.push({
this.refs.resources.push({
...r,
originalIdentifier: r.identifier,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use response data instead of fixture data to update refs.

The current implementation uses fixture data to update refs, which might miss server-generated fields like IDs or timestamps. This is inconsistent with other methods that correctly use response data.

-// Store created resources in result
-for (const r of this.fixtures.resources) {
-  this.refs.resources.push({
-    ...r,
-    originalIdentifier: r.identifier,
-  });
-}
+// Store created resources from responses
+for (let i = 0; i < results.length; i++) {
+  const response = results[i].fetchResponse;
+  const fixture = this.fixtures.resources[i];
+  if (response.data) {
+    this.refs.resources.push({
+      id: response.data.id,
+      identifier: response.data.identifier || fixture.identifier,
+      name: response.data.name || fixture.name,
+      type: response.data.type || fixture.type,
+      config: response.data.config || fixture.config,
+      originalIdentifier: fixture.identifier,
+    });
+  }
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const r of this.fixtures.resources) {
this.cache.resources.push({
this.refs.resources.push({
...r,
originalIdentifier: r.identifier,
});
}
// Store created resources from responses
for (let i = 0; i < results.length; i++) {
const response = results[i].fetchResponse;
const fixture = this.fixtures.resources[i];
if (response.data) {
this.refs.resources.push({
id: response.data.id,
identifier: response.data.identifier || fixture.identifier,
name: response.data.name || fixture.name,
type: response.data.type || fixture.type,
config: response.data.config || fixture.config,
originalIdentifier: fixture.identifier,
});
}
}
🤖 Prompt for AI Agents
In e2e/api/entities-builder.ts around lines 99 to 104, the code updates refs
using fixture data instead of the actual response data, which can omit
server-generated fields like IDs or timestamps. Modify the loop to iterate over
the response data received from the server rather than the fixture data, and
update this.refs.resources with the properties from the response objects to
ensure all server-generated fields are included.

@j7m4 j7m4 merged commit b0eb05f into ctrlplanedev:main Jun 4, 2025
5 checks passed
@j7m4 j7m4 deleted the j7m4/e2e-job-flows branch June 4, 2025 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants