8000 fix(connectors): fix webhook idempotency key and tests by paul-nicolas · Pull Request #445 · formancehq/payments · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

fix(connectors): fix webhook idempotency key and tests #445

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 17 commits into from
May 14, 2025

Conversation

paul-nicolas
Copy link
Contributor

Description

This PR introduces two fixes:

Webhook Idempotency Key

Context

As of today, we have a kind of Idempotency Key implemented for webhooks. It's filled in the response of the TranslateWebhook call to the plugin and is pass inside the workflowID of temporal with a policy of rejecting duplicates in order to not store the responses in case we already processed this event in the past.

This is partially working because we do not retain all workflows in temporal forever. So if a webhook is replayed after the workflow is deleted from temporal, it will process it again.

Solution

The solution I came by is:

  • We add a new column to the webhook storage table called idempotency key. This way, we depend on our storage instead of temporal.
  • The addition of a new plugin function called VerifyWebhook which as its name says, verify that the webhook is valid though signatures etc... and also send as a response an IdempotencyKey, which can be either what the PSP gave us (for example wise and column give us an id for the event), the sha256 of the payload or nothing.

Plugins tests

Context

In most of the plugin tests, we are using the Plugin struct to test the main and sub functions. Since this Plugin structure have in composition the base plugin, we can't tests if we make a type in the override of the function, so we need to ensure that all plugin interface function that we implemented are the right ones.

Solution

The solution I came by is:

  • Use the models.Plugin interface instead of directly the structure inside all plugin tests. That way, we ensure that all function are implemented correctly.

Fixes PMNT-105
Fixes PMNT-106

Copy link
Contributor
coderabbitai bot commented May 14, 2025
## Walkthrough

This change introduces a new webhook verification mechanism across the plugin architecture, adding a `VerifyWebhook` method to the plugin interface and implementations. The webhook idempotency key is now generated and enforced at the plugin level, stored in the database, and used in the workflow logic. Tests and mocks are updated to cover the new verification flow, and a database migration adds a unique idempotency key constraint.

## Changes

| Files/Paths                                                                                          | Change Summary                                                                                                                                                                                                                                                                                                                                                                               |
|------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| internal/models/plugin.go, internal/models/plugin_generated.go, internal/models/webhooks.go          | Added `VerifyWebhook` to plugin interface, new request/response types, updated mocks, and added `IdempotencyKey` to webhook models.                                                                                                                                                                                                                                                          |
| internal/connectors/engine/activities/activity.go, internal/connectors/engine/activities/plugin_verify_webhook.go, internal/connectors/engine/activities/plugin_verify_webhook_test.go | Added new Temporal activity for webhook verification, corresponding helper, and comprehensive tests for plugin verification logic and error handling.                                                                                                                                                                                                                                         |
| internal/connectors/engine/workflow/handle_webhooks.go, internal/connectors/engine/workflow/handle_webhooks_test.go                  | Integrated plugin webhook verification into the workflow, updated idempotency key handling, and extended tests for verification and error scenarios.                                                                                                                                                                                                                                         |
| internal/connectors/plugins/base_plugin.go, internal/connectors/plugins/public/*/plugin.go, internal/connectors/plugins/public/*/webhooks.go, internal/connectors/plugins/public/*/webhook.go, internal/connectors/plugins/public/*/webhooks_test.go, internal/connectors/plugins/public/*/plugin_test.go | Implemented `VerifyWebhook` in all plugin types, separated verification from translation logic, updated plugin test setups, and added/updated verification tests.                                                                                                                                                                                      |
| internal/connectors/plugins/registry/wrapper.go                                                      | Wrapped plugin `VerifyWebhook` with tracing and logging in the registry.                                                                                                                                                                                                                                                                                                                     |
| internal/storage/migrations/16-webhooks-idempotency-key.sql, internal/storage/migrations/migrations.go                       | Added SQL migration to introduce a unique `idempotency_key` column to the webhooks table and registered the migration in Go code.                                                                                                                                                                                                                                                           |
| internal/storage/webhooks.go, internal/storage/webhooks_test.go                                                   | Updated storage logic to handle the new `IdempotencyKey` field in webhooks, and added tests for null and duplicate idempotency key scenarios.                                                                                                                                                                                                                                               |
| internal/connectors/plugins/public/*_test.go, internal/connectors/plugins/public/*/plugin_test.go, internal/connectors/plugins/public/*/webhooks_test.go, internal/connectors/plugins/public/column/reverse_payout_test.go, internal/connectors/plugins/public/column/transfers_test.go, internal/connectors/plugins/public/wise/profiles_test.go, internal/connectors/plugins/public/wise/uninstall_test.go | Refactored plugin test initialization to use interface types and centralized mock setup, updated tests to use new verification and idempotency key logic, and removed redundant or outdated test code.                                                                                                                                                |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Workflow
    participant Activities
    participant PluginRegistry
    participant Plugin
    participant Storage

    Workflow->>Activities: PluginVerifyWebhook(connectorID, VerifyWebhookRequest)
    Activities->>PluginRegistry: GetPlugin(connectorID)
    PluginRegistry->>Plugin: VerifyWebhook(VerifyWebhookRequest)
    Plugin-->>PluginRegistry: VerifyWebhookResponse (with IdempotencyKey)
    PluginRegistry-->>Activities: VerifyWebhookResponse
    Activities-->>Workflow: VerifyWebhookResponse

    alt Verification successful
        Workflow->>Storage: StoreWebhook(with IdempotencyKey)
    else Verification failed
        Workflow-->>Workflow: Return error
    end

Assessment against linked issues

Objective Addressed Explanation
Fix webhook idempotency not working today because it's relying on temporal workflow ID (PMNT-105)
Ensure that all plugin main functions are tested (and not only the sub functions) (PMNT-106)

Suggested reviewers

  • fguery
  • laouji

Poem

🐇
Webhooks now are verified,
With keys unique, they're classified.
Plugins check and tests abound,
Idempotency safe and sound.
Migrations run, the schema grows,
A bunny hops where idempotency flows!
🥕


<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> - CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> 
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=formancehq/payments&utm_content=445):

- 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](https://www.coderabbit.ai/contact-us/support) 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](https://docs.coderabbit.ai/finishing-touches/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.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

@paul-nicolas
Copy link
Contributor Author

@coderabbitai review

8000
Copy link
Contributor
coderabbitai bot commented May 14, 2025
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@paul-nicolas
Copy link
Contributor Author

@coderabbitai review

Copy link
Contributor
coderabbitai bot commented May 14, 2025
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@paul-nicolas paul-nicolas marked this pull request as ready for review May 14, 2025 13:54
@paul-nicolas paul-nicolas requested a review from a team as a code owner May 14, 2025 13:54
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: 6

🔭 Outside diff range comments (1)
internal/connectors/plugins/public/atlar/payments_test.go (1)

1-289: 💡 Verification agent

🧩 Analysis chain

Verify the new VerifyWebhook function implementation.

While the test structure changes look good, I don't see the implementation of the new VerifyWebhook function mentioned in the PR objectives. This function is crucial for the webhook idempotency key mechanism.


🏁 Script executed:

#!/bin/bash
# Check if the VerifyWebhook function is implemented in the Atlar plugin
echo "Searching for VerifyWebhook implementation in Atlar plugin..."
rg -A 5 "func \(\*Plugin\) VerifyWebhook" internal/connectors/plugins/public/atlar/

# Check if the VerifyWebhook function is defined in the plugin interface
echo "Checking if VerifyWebhook is defined in the plugin interface..."
rg -A 3 "VerifyWebhook" internal/models/plugin.go

Length of output: 744


Implement the missing VerifyWebhook method in the Atlar plugin

The plugin interface (internal/models/plugin.go) defines:

  • VerifyWebhook(context.Context, VerifyWebhookRequest) (VerifyWebhookResponse, error)

However, I couldn’t find any implementation of:

  • func (*Plugin) VerifyWebhook(...) under internal/connectors/plugins/public/atlar/

Please add the VerifyWebhook method to your Plugin type and cover it with tests to support the webhook idempotency key mechanism.

🧹 Nitpick comments (11)
internal/connectors/engine/workflow/handle_webhooks.go (1)

1-123: Consider documenting the new idempotency mechanism.

The implementation of the webhook verification and idempotency changes is solid. However, it would be beneficial to add documentation that explains the overall idempotency approach and how it differs from the previous implementation.

Consider adding a comment like:

 // HandleWebhooks defines the input for webhook handling workflow
 type HandleWebhooks struct {
 	ConnectorID models.ConnectorID
 	URLPath     string
 	Webhook     models.Webhook
 }
 
+// runHandleWebhooks processes incoming webhooks by:
+// 1. Finding the matching webhook configuration
+// 2. Verifying the webhook and obtaining an idempotency key for deduplication
+// 3. Storing the webhook with its idempotency key in persistent storage
+// 4. Translating the webhook to platform objects (accounts, payments)
+// 5. Storing the translated objects
+// 
+// The idempotency key is provided by the plugin's VerifyWebhook method and stored
+// with the webhook, which prevents duplicate processing even if Temporal workflows
+// are deleted after completion.
 func (w Workflow) runHandleWebhooks(
 	ctx workflow.Context,
 	handleWebhooks HandleWebhooks,
 ) error {
internal/connectors/plugins/public/wise/plugin_test.go (1)

104-168: Good separation of webhook verification from translation

The implementation of a dedicated VerifyWebhook method with proper tests improves the design by separating concerns: authentication/verification vs. content translation. The tests properly validate the error cases for missing headers and the successful case returning an idempotency key.

Consider extracting the common setup code between the "verifying webhook" and "translate webhook" contexts to reduce duplication, perhaps by moving the shared mock client setup and test data generation to a higher-level BeforeEach block.

internal/connectors/plugins/public/column/bank_account_creation_test.go (2)

20-26: Call ctrl.Finish() to guarantee mock expectations are asserted

gomock.NewController should be paired with ctrl.Finish() (or DeferCleanup(ctrl.Finish) in Ginkgo-v2) to ensure that all expectations are checked and to release resources.
Without it, silent expectation leaks can creep in and later tests may pass unexpectedly.

 BeforeEach(func() {
     ctrl := gomock.NewController(GinkgoT())
+    DeferCleanup(ctrl.Finish)   // make sure gomock verifies expectations

28-119: Consider a table-driven test to reduce duplication

The 7 “missing field” scenarios follow the same Arrange-Act-Assert pattern with only the request body and expected error changing. Converting them into a single DescribeTable (or a loop in plain Go tests) will:

  • Cut ~120 lines of repetitive code
  • Make it trivial to add new validation cases
  • Keep the intent of each case in one concise row

This is optional but will improve maintainability.

internal/connectors/plugins/public/wise/webhooks_test.go (1)

64-75: Assertion on created webhooks may become brittle

The test hard-codes:

Expect(res.Others).To(HaveLen(1))

If additional supported webhooks are introduced in the production code (e.g., new event types), this expectation will start failing although the behaviour is still correct.

Two safer alternatives:

  1. Assert that at least one webhook was created and that the expected one is present.
  2. Build the expectation from p.supportedWebhooks so the test automatically adapts.
-Expect(res.Others).To(HaveLen(1))
+Expect(res.Others).ToNot(BeEmpty())
+Expect(res.Others).To(ContainElement(
+    SatisfyAll(
+        HaveField("ID", expectedWebhookResponseID),
+    ),
+))
internal/connectors/plugins/public/adyen/webhooks.go (1)

56-76: Avoid double JSON parsing for performance

verifyWebhook parses the body via TranslateWebhook, and translateStandardWebhook parses it again.
Passing the already parsed webhooks structure (or the computed idempotency key) downstream would:

  • Eliminate redundant CPU / allocations
  • Guarantee both phases operate on identical data

Consider enhancing models.VerifyWebhookResponse to optionally carry the parsed notifications.

internal/connectors/plugins/public/column/webhook.go (1)

300-328: Consider avoiding duplicate JSON unmarshaling

The webhook body is unmarsaled both in verifyWebhook (line 321) and again in translateWebhook (line 347). This duplication is inefficient for large webhook payloads.

Consider refactoring to unmarshal only once, perhaps by:

  1. Adding the parsed result to the response
  2. Caching the parsed result within the plugin instance
  3. Or optionally modifying the workflow to pass the parsed data between activities
internal/connectors/engine/activities/plugin_verify_webhook.go (1)

30-39: Minor optimization opportunity in helper function

The function creates a response struct, passes it to executeActivity, and then returns a pointer to it.

This could be slightly simplified:

func PluginVerifyWebhook(ctx workflow.Context, connectorID models.ConnectorID, request models.VerifyWebhookRequest) (*models.VerifyWebhookResponse, error) {
-	ret := models.VerifyWebhookResponse{}
-	if err := executeActivity(ctx, PluginVerifyWebhookActivity, &ret, VerifyWebhookRequest{
+	var ret models.VerifyWebhookResponse
+	if err := executeActivity(ctx, PluginVerifyWebhookActivity, ret, VerifyWebhookRequest{
		ConnectorID: connectorID,
		Req:         request,
	}); err != nil {
		return nil, err
	}
-	return &ret, nil
+	return ret, nil
}
internal/connectors/plugins/public/column/payouts_test.go (1)

667-714: Consider using interface method for consistency.

While the tests now generally use the interface type, this specific test directly calls a concrete method on a local variable of type Plugin. Consider refactoring this to use the interface method if possible, maintaining consistency with the rest of the test suite.

-		p := Plugin{}
-		result := p.mapTransactionStatus(tc.status)
+		// Either expose the method through the interface or 
+		// use a type assertion if the method is internal
+		plugin := &Plugin{}
+		plg := models.Plugin(plugin)
+		result := plugin.mapTransactionStatus(tc.status)
internal/connectors/plugins/public/column/webhook_test.go (2)

150-160: Mock method name should follow the interface – avoid brittle stubbing

The expectation is set on an unexported method name
verifyWebhookSignature. If the interface changes (e.g. exported
VerifySignature) the mock generator will rename the method and the
test will compile but silently stop verifying the call.

Consider:

verifierMock.EXPECT().
    VerifySignature(gomock.Any(), gomock.Any(), gomock.Any()).
    Return(errors.New("test error"))

and align the interface accordingly.


162-184: Hard-coding the idempotency key couples the test to payload internals

pointer.For("1") assumes the plugin will always return exactly the
payload’s id field. If the idempotency strategy changes (e.g. SHA-256
hash of the body) the test will fail even though behaviour is still
correct.

Instead, check non-nil presence:

Expect(res.WebhookIdempotencyKey).ToNot(BeNil())
Expect(*res.WebhookIdempotencyKey).To(Equal("1")) // only if the spec mandates it

or expose a helper constant from the implementation.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 296ec3f and b772cd3.

📒 Files selected for processing (92)
  • internal/connectors/engine/activities/activity.go (1 hunks)
  • internal/connectors/engine/activities/plugin_verify_webhook.go (1 hunks)
  • internal/connectors/engine/activities/plugin_verify_webhook_test.go (1 hunks)
  • internal/connectors/engine/workflow/handle_webhooks.go (2 hunks)
  • internal/connectors/engine/workflow/handle_webhooks_test.go (7 hunks)
  • internal/connectors/plugins/base_plugin.go (1 hunks)
  • internal/connectors/plugins/public/adyen/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/adyen/plugin.go (1 hunks)
  • internal/connectors/plugins/public/adyen/webhooks.go (3 hunks)
  • internal/connectors/plugins/public/adyen/webhooks_test.go (8 hunks)
  • internal/connectors/plugins/public/atlar/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/atlar/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/atlar/bank_account_creation_test.go (1 hunks)
  • internal/connectors/plugins/public/atlar/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/atlar/payments_test.go (1 hunks)
  • internal/connectors/plugins/public/atlar/payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/bankingcircle/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/bankingcircle/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/bankingcircle/bank_account_creation_test.go (1 hunks)
  • internal/connectors/plugins/public/bankingcircle/payments_test.go (1 hunks)
  • internal/connectors/plugins/public/bankingcircle/payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/bankingcircle/transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/column/accounts_test.go (3 hunks)
  • internal/connectors/plugins/public/column/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/column/bank_account_creation_test.go (1 hunks)
  • internal/connectors/plugins/public/column/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/column/payments_test.go (1 hunks)
  • internal/connectors/plugins/public/column/payouts_test.go (5 hunks)
  • internal/connectors/plugins/public/column/plugin.go (1 hunks)
  • internal/connectors/plugins/public/column/plugin_test.go (2 hunks)
  • internal/connectors/plugins/public/column/reverse_payout_test.go (2 hunks)
  • internal/connectors/plugins/public/column/transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/column/webhook.go (1 hunks)
  • internal/connectors/plugins/public/column/webhook_test.go (5 hunks)
  • internal/connectors/plugins/public/currencycloud/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/currencycloud/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/currencycloud/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/currencycloud/payments_test.go (1 hunks)
  • internal/connectors/plugins/public/currencycloud/payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/currencycloud/transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/generic/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/generic/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/generic/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/generic/payments_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/bank_account_creation_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/payments_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/plugin.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/users_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/webhooks.go (4 hunks)
  • internal/connectors/plugins/public/mangopay/webhooks_test.go (2 hunks)
  • internal/connectors/plugins/public/modulr/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/modulr/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/modulr/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/modulr/payments_test.go (2 hunks)
  • internal/connectors/plugins/public/modulr/payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/modulr/transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/moneycorp/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/moneycorp/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/moneycorp/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/moneycorp/payments_test.go (2 hunks)
  • internal/connectors/plugins/public/moneycorp/payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/moneycorp/transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/stripe/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/stripe/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/stripe/create_payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/stripe/create_transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/stripe/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/stripe/reverse_transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/payments_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/plugin.go (1 hunks)
  • internal/connectors/plugins/public/wise/plugin_test.go (2 hunks)
  • internal/connectors/plugins/public/wise/profiles_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/uninstall_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/webhooks_test.go (3 hunks)
  • internal/connectors/plugins/registry/wrapper.go (1 hunks)
  • internal/models/plugin.go (2 hunks)
  • internal/models/plugin_generated.go (2 hunks)
  • internal/models/webhooks.go (1 hunks)
  • internal/storage/migrations/16-webhooks-idempotency-key.sql (1 hunks)
  • internal/storage/migrations/migrations.go (2 hunks)
  • internal/storage/webhooks.go (2 hunks)
  • internal/storage/webhooks_test.go (3 hunks)
🧰 Additional context used
🧠 Learnings (6)
internal/connectors/plugins/public/moneycorp/balances_test.go (1)
Learnt from: laouji
PR: formancehq/payments#193
File: internal/connectors/plugins/public/dummypay/client/client.go:104-131
Timestamp: 2024-12-12T12:45:27.164Z
Learning: The `Balance` struct in `internal/connectors/plugins/public/dummypay/client/client.go` is only used in tests, so changing `AmountInMinors` from `int64` to `*big.Int` is not necessary.
internal/connectors/plugins/public/mangopay/balances_test.go (1)
Learnt from: laouji
PR: formancehq/payments#193
File: internal/connectors/plugins/public/dummypay/client/client.go:104-131
Timestamp: 2024-12-12T12:45:27.164Z
Learning: The `Balance` struct in `internal/connectors/plugins/public/dummypay/client/client.go` is only used in tests, so changing `AmountInMinors` from `int64` to `*big.Int` is not necessary.
internal/connectors/plugins/public/generic/balances_test.go (1)
Learnt from: laouji
PR: formancehq/payments#193
File: internal/connectors/plugins/public/dummypay/client/client.go:104-131
Timestamp: 2024-12-12T12:45:27.164Z
Learning: The `Balance` struct in `internal/connectors/plugins/public/dummypay/client/client.go` is only used in tests, so changing `AmountInMinors` from `int64` to `*big.Int` is not necessary.
internal/connectors/plugins/public/wise/balances_test.go (1)
Learnt from: laouji
PR: formancehq/payments#193
File: internal/connectors/plugins/public/dummypay/client/client.go:104-131
Timestamp: 2024-12-12T12:45:27.164Z
Learning: The `Balance` struct in `internal/connectors/plugins/public/dummypay/client/client.go` is only used in tests, so changing `AmountInMinors` from `int64` to `*big.Int` is not necessary.
internal/connectors/plugins/public/modulr/balances_test.go (1)
Learnt from: laouji
PR: formancehq/payments#193
File: internal/connectors/plugins/public/dummypay/client/client.go:104-131
Timestamp: 2024-12-12T12:45:27.164Z
Learning: The `Balance` struct in `internal/connectors/plugins/public/dummypay/client/client.go` is only used in tests, so changing `AmountInMinors` from `int64` to `*big.Int` is not necessary.
internal/connectors/plugins/public/currencycloud/balances_test.go (1)
Learnt from: laouji
PR: formancehq/payments#193
File: internal/connectors/plugins/public/dummypay/client/client.go:104-131
Timestamp: 2024-12-12T12:45:27.164Z
Learning: The `Balance` struct in `internal/connectors/plugins/public/dummypay/client/client.go` is only used in tests, so changing `AmountInMinors` from `int64` to `*big.Int` is not necessary.
🧬 Code Graph Analysis (52)
internal/connectors/plugins/public/moneycorp/payments_test.go (1)
internal/connectors/plugins/public/moneycorp/plugin.go (1)
  • Plugin (22-29)
internal/connectors/plugins/public/bankingcircle/transfers_test.go (4)
internal/connectors/plugins/public/bankingcircle/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/models/plugin.go (1)
  • Plugin (14-21)
internal/connectors/plugins/public/bankingcircle/plugin.go (1)
  • Plugin (22-29)
internal/models/payment_initiations.go (1)
  • PSPPaymentInitiation (11-33)
internal/connectors/plugins/public/mangopay/payments_test.go (2)
internal/connectors/plugins/public/mangopay/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/mangopay/plugin.go (1)
  • Plugin (25-33)
internal/connectors/plugins/public/atlar/external_accounts_test.go (1)
internal/connectors/plugins/public/atlar/client/client_generated.go (2)
  • MockClient (28-32)
  • NewMockClient (40-44)
internal/connectors/plugins/public/moneycorp/accounts_test.go (1)
internal/connectors/plugins/public/moneycorp/plugin.go (1)
  • Plugin (22-29)
internal/connectors/plugins/public/mangopay/webhooks.go (2)
internal/models/payments.go (1)
  • Payment (87-127)
internal/connectors/plugins/public/mangopay/client/transactions.go (1)
  • Payment (14-31)
internal/connectors/plugins/public/wise/uninstall_test.go (2)
internal/connectors/plugins/public/wise/plugin.go (1)
  • Plugin (34-43)
internal/connectors/plugins/public/wise/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/modulr/external_accounts_test.go (2)
internal/connectors/plugins/public/modulr/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/modulr/plugin.go (1)
  • Plugin (22-29)
internal/connectors/plugins/public/wise/accounts_test.go (1)
internal/connectors/plugins/public/wise/plugin.go (1)
  • Plugin (34-43)
internal/connectors/plugins/public/column/plugin.go (3)
internal/models/plugin.go (3)
  • Plugin (14-21)
  • VerifyWebhookRequest (158-161)
  • VerifyWebhookResponse (163-165)
internal/connectors/plugins/public/mangopay/plugin.go (1)
  • Plugin (25-33)
internal/connectors/plugins/errors.go (1)
  • ErrNotYetInstalled (9-9)
internal/models/webhooks.go (1)
internal/models/idempotency.go (1)
  • IdempotencyKey (11-19)
internal/connectors/engine/workflow/handle_webhooks.go (4)
internal/connectors/engine/activities/plugin_verify_webhook.go (2)
  • PluginVerifyWebhook (30-39)
  • VerifyWebhookRequest (10-13)
internal/models/plugin.go (1)
  • VerifyWebhookRequest (158-161)
internal/models/webhooks.go (3)
  • Webhook (33-41)
  • PSPWebhook (25-31)
  • BasicAuth (20-23)
internal/models/idempotency.go (1)
  • IdempotencyKey (11-19)
internal/connectors/plugins/public/moneycorp/external_accounts_test.go (1)
internal/connectors/plugins/public/moneycorp/plugin.go (1)
  • Plugin (22-29)
internal/connectors/plugins/public/adyen/plugin.go (2)
internal/models/plugin.go (3)
  • Plugin (14-21)
  • VerifyWebhookRequest (158-161)
  • VerifyWebhookResponse (163-165)
internal/connectors/plugins/errors.go (1)
  • ErrNotYetInstalled (9-9)
internal/connectors/plugins/public/column/payments_test.go (4)
internal/connectors/plugins/public/column/client/http_generated.go (2)
  • MockHTTPClient (21-25)
  • NewMockHTTPClient (33-37)
internal/models/plugin.go (1)
  • Plugin (14-21)
internal/connectors/plugins/public/column/plugin.go (2)
  • Plugin (69-79)
  • New (81-102)
internal/connectors/plugins/public/column/client/client.go (1)
  • New (67-87)
internal/connectors/plugins/public/modulr/transfers_test.go (2)
internal/connectors/plugins/public/modulr/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/modulr/plugin.go (1)
  • Plugin (22-29)
internal/connectors/plugins/public/generic/accounts_test.go (2)
internal/connectors/plugins/public/generic/client/client_generated.go (2)
  • MockClient (22-26)
  • NewMockClient (34-38)
internal/connectors/plugins/public/generic/plugin.go (1)
  • Plugin (22-29)
internal/connectors/plugins/public/column/plugin_test.go (3)
internal/models/plugin.go (1)
  • Plugin (14-21)
internal/connectors/plugins/public/column/plugin.go (3)
  • Plugin (69-79)
  • New (81-102)
  • ProviderName (16-16)
internal/connectors/plugins/public/dummypay/plugin.go (2)
  • Plugin (21-27)
  • New (29-42)
internal/connectors/plugins/public/bankingcircle/bank_account_creation_test.go (2)
internal/connectors/plugins/public/bankingcircle/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/bankingcircle/plugin.go (1)
  • Plugin (22-29)
internal/connectors/plugins/public/adyen/accounts_test.go (2)
internal/connectors/plugins/public/adyen/client/client_generated.go (2)
  • MockClient (23-27)
  • NewMockClient (35-39)
internal/connectors/plugins/public/adyen/plugin.go (1)
  • Plugin (23-33)
internal/storage/webhooks.go (2)
internal/models/idempotency.go (1)
  • IdempotencyKey (11-19)
internal/models/webhooks.go (1)
  • Webhook (33-41)
internal/connectors/plugins/public/wise/balances_test.go (1)
internal/connectors/plugins/public/wise/plugin.go (1)
  • Plugin (34-43)
internal/connectors/plugins/public/mangopay/external_accounts_test.go (2)
internal/connectors/plugins/public/mangopay/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/mangopay/plugin.go (1)
  • Plugin (25-33)
internal/connectors/plugins/public/atlar/payments_test.go (1)
internal/connectors/plugins/public/atlar/client/client_generated.go (2)
  • MockClient (28-32)
  • NewMockClient (40-44)
internal/connectors/plugins/public/wise/profiles_test.go (1)
internal/connectors/plugins/public/wise/plugin.go (1)
  • Plugin (34-43)
internal/connectors/plugins/public/column/external_accounts_test.go (4)
internal/connectors/plugins/public/column/client/http_generated.go (2)
  • MockHTTPClient (21-25)
  • NewMockHTTPClient (33-37)
internal/models/plugin.go (1)
  • Plugin (14-21)
internal/connectors/plugins/public/column/plugin.go (2)
  • Plugin (69-79)
  • New (81-102)
internal/connectors/plugins/public/column/client/client.go (1)
  • New (67-87)
internal/connectors/plugins/public/modulr/accounts_test.go (2)
internal/connectors/plugins/public/modulr/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/modulr/plugin.go (1)
  • Plugin (22-29)
internal/connectors/plugins/public/wise/external_accounts_test.go (1)
internal/connectors/plugins/public/wise/plugin.go (1)
  • Plugin (34-43)
internal/connectors/plugins/base_plugin.go (3)
internal/models/plugin.go (2)
  • VerifyWebhookRequest (158-161)
  • VerifyWebhookResponse (163-165)
internal/connectors/engine/activities/plugin_verify_webhook.go (1)
  • VerifyWebhookRequest (10-13)
internal/connectors/plugins/errors.go (1)
  • ErrNotImplemented (8-8)
internal/connectors/plugins/public/currencycloud/transfers_test.go (2)
internal/connectors/plugins/public/currencycloud/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/currencycloud/plugin.go (1)
  • Plugin (22-29)
internal/storage/webhooks_test.go (3)
internal/models/idempotency.go (1)
  • IdempotencyKey (11-19)
internal/models/webhooks.go (1)
  • Webhook (33-41)
internal/storage/error.go (1)
  • ErrDuplicateKeyValue (18-18)
internal/connectors/plugins/public/stripe/external_accounts_test.go (2)
internal/connectors/plugins/public/stripe/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/stripe/plugin.go (1)
  • Plugin (22-28)
internal/connectors/plugins/public/bankingcircle/accounts_test.go (2)
internal/connectors/plugins/public/bankingcircle/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/bankingcircle/plugin.go (1)
  • Plugin (22-29)
internal/connectors/plugins/public/atlar/accounts_test.go (1)
internal/connectors/plugins/public/atlar/client/client_generated.go (2)
  • MockClient (28-32)
  • NewMockClient (40-44)
internal/connectors/plugins/public/generic/external_accounts_test.go (2)
internal/connectors/plugins/public/generic/client/client_generated.go (2)
  • MockClient (22-26)
  • NewMockClient (34-38)
internal/connectors/plugins/public/generic/plugin.go (1)
  • Plugin (22-29)
internal/connectors/plugins/public/mangopay/bank_account_creation_test.go (2)
internal/connectors/plugins/public/mangopay/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/mangopay/plugin.go (1)
  • Plugin (25-33)
internal/connectors/plugins/public/mangopay/users_test.go (2)
internal/connectors/plugins/public/mangopay/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/mangopay/plugin.go (1)
  • Plugin (25-33)
internal/connectors/plugins/public/mangopay/accounts_test.go (2)
internal/connectors/plugins/public/mangopay/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/mangopay/plugin.go (1)
  • Plugin (25-33)
internal/connectors/plugins/public/currencycloud/payments_test.go (2)
internal/connectors/plugins/public/currencycloud/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/currencycloud/plugin.go (1)
  • Plugin (22-29)
internal/connectors/engine/activities/plugin_verify_webhook_test.go (8)
internal/connectors/engine/activities/activity.go (2)
  • Activities (17-26)
  • New (348-364)
internal/connectors/engine/plugins/plugin_generated.go (2)
  • MockPlugins (21-25)
  • NewMockPlugins (33-37)
internal/storage/storage_generated.go (2)
  • MockStorage (24-28)
  • NewMockStorage (36-40)
internal/models/plugin.go (2)
  • VerifyWebhookResponse (163-165)
  • VerifyWebhookRequest (158-161)
internal/models/plugin_generated.go (2)
  • MockPlugin (20-24)
  • NewMockPlugin (32-36)
internal/connectors/engine/activities/plugin_verify_webhook.go (2)
  • VerifyWebhookRequest (10-13)
  • PluginVerifyWebhook (30-39)
internal/connectors/engine/activities/errors.go (2)
  • ErrTypeDefault (18-18)
  • ErrTypeUnimplemented (21-21)
internal/connectors/plugins/errors.go (1)
  • ErrNotImplemented (8-8)
internal/models/plugin.go (2)
internal/connectors/engine/activities/plugin_verify_webhook.go (1)
  • VerifyWebhookRequest (10-13)
internal/models/webhooks.go (3)
  • Webhook (33-41)
  • PSPWebhook (25-31)
  • WebhookConfig (11-18)
internal/connectors/plugins/public/generic/payments_test.go (2)
internal/connectors/plugins/public/generic/client/client_generated.go (2)
  • MockClient (22-26)
  • NewMockClient (34-38)
internal/connectors/plugins/public/generic/plugin.go (1)
  • Plugin (22-29)
internal/connectors/plugins/public/currencycloud/accounts_test.go (2)
internal/connectors/plugins/public/currencycloud/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/currencycloud/plugin.go (1)
  • Plugin (22-29)
internal/connectors/plugins/public/bankingcircle/payments_test.go (2)
internal/connectors/plugins/public/bankingcircle/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/bankingcircle/plugin.go (1)
  • Plugin (22-29)
internal/connectors/plugins/public/stripe/reverse_transfers_test.go (2)
internal/connectors/plugins/public/stripe/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/connectors/plugins/public/stripe/plugin.go (1)
  • Plugin (22-28)
internal/models/plugin_generated.go (3)
internal/models/plugin.go (2)
  • VerifyWebhookRequest (158-161)
  • VerifyWebhookResponse (163-165)
internal/connectors/engine/activities/plugin_verify_webhook.go (1)
  • VerifyWebhookRequest (10-13)
pkg/testserver/server.go (1)
  • T (32-37)
internal/connectors/plugins/public/column/webhook.go (6)
internal/models/plugin.go (5)
  • Plugin (14-21)
  • VerifyWebhookRequest (158-161)
  • VerifyWebhookResponse (163-165)
  • TranslateWebhookRequest (142-146)
  • TranslateWebhookResponse (154-156)
internal/connectors/plugins/public/wise/plugin.go (3)
  • Plugin (34-43)
  • HeadersSignature (26-26)
  • ErrWebhookHeaderXSignatureMissing (30-30)
internal/connectors/plugins/public/column/plugin.go (1)
  • Plugin (69-79)
internal/models/webhooks.go (1)
  • Webhook (33-41)
internal/connectors/plugins/public/column/client/error.go (3)
  • ErrWebhookHeaderXSignatureMissing (10-10)
  • ErrWebhookConfigInvalid (11-11)
  • ErrWebhookConfigSecretMissing (13-13)
internal/connectors/plugins/public/column/client/webhook.go (1)
  • WebhookEvent (71-76)
internal/connectors/plugins/public/atlar/bank_account_creation_test.go (1)
internal/connectors/plugins/public/atlar/client/client_generated.go (2)
  • MockClient (28-32)
  • NewMockClient (40-44)
internal/connectors/plugins/public/wise/payments_test.go (1)
internal/connectors/plugins/public/wise/plugin.go (1)
  • Plugin (34-43)
internal/connectors/plugins/public/moneycorp/payouts_test.go (3)
internal/connectors/plugins/public/moneycorp/client/client_generated.go (2)
  • MockClient (21-25)
  • NewMockClient (33-37)
internal/models/plugin.go (1)
  • Plugin (14-21)
internal/connectors/plugins/public/moneycorp/plugin.go (1)
  • Plugin (22-29)
internal/connectors/engine/activities/plugin_verify_webhook.go (2)
internal/models/plugin.go (2)
  • VerifyWebhookRequest (158-161)
  • VerifyWebhookResponse (163-165)
internal/connectors/engine/activities/activity.go (1)
  • Activities (17-26)
internal/connectors/plugins/public/column/webhook_test.go (8)
internal/models/plugin.go (3)
  • Plugin (14-21)
  • VerifyWebhookRequest (158-161)
  • VerifyWebhookResponse (163-165)
internal/connectors/plugins/public/column/plugin.go (2)
  • Plugin (69-79)
  • New (81-102)
internal/connectors/plugins/public/column/client/http_generated.go (2)
  • MockHTTPClient (21-25)
  • NewMockHTTPClient (33-37)
internal/connectors/plugins/public/column/webhookverifier_generated.go (2)
  • MockWebhookVerifier (19-23)
  • NewMockWebhookVerifier (31-35)
internal/connectors/plugins/public/column/client/webhook.go (2)
  • EventSubscription (78-87)
  • EventCategoryACHTransferSettled (28-28)
internal/connectors/engine/workflow/create_webhooks.go (1)
  • CreateWebhooks (13-17)
internal/models/webhooks.go (3)
  • WebhookConfig (11-18)
  • Webhook (33-41)
  • PSPWebhook (25-31)
internal/connectors/plugins/public/column/client/error.go (1)
  • ErrWebhookTypeUnknown (12-12)
🔇 Additional comments (158)
internal/models/webhooks.go (1)

35-37: LGTM: Adding IdempotencyKey field to support webhook deduplication

The addition of the IdempotencyKey field to the Webhook struct is well-placed and properly typed as an optional *string. This change supports the new webhook idempotency mechanism described in the PR objectives, shifting from Temporal workflow IDs to persistent storage for deduplication.

internal/storage/migrations/16-webhooks-idempotency-key.sql (1)

1-4: LGTM: Well-structured migration for webhook idempotency

The migration adds the idempotency_key column to the webhooks table and creates a unique index on the combination of connector_id and idempotency_key. This properly supports the webhook deduplication mechanism by enforcing uniqueness at the database level.

internal/connectors/plugins/public/mangopay/webhooks.go (4)

208-210: LGTM: Removed IdempotencyKey from translateTransfer response

Correctly removed the IdempotencyKey field from the WebhookResponse returned by translateTransfer. This aligns with the architectural change to separate webhook verification and idempotency key generation from translation logic.


251-253: LGTM: Removed IdempotencyKey from translatePayout response

Correctly removed the IdempotencyKey field from the WebhookResponse returned by translatePayout. This aligns with the architectural change to separate webhook verification and idempotency key generation from translation logic.


290-292: LGTM: Removed IdempotencyKey from translatePayin response

Correctly removed the IdempotencyKey field from the WebhookResponse returned by translatePayin. This aligns with the architectural change to separate webhook verification and idempotency key generation from translation logic.


334-336: LGTM: Removed IdempotencyKey from translateRefund response

Correctly removed the IdempotencyKey field from the WebhookResponse returned by translateRefund. This aligns with the architectural change to separate webhook verification and idempotency key generation from translation logic.

internal/connectors/engine/activities/activity.go (1)

90-93: LGTM: Added PluginVerifyWebhook activity

Added a new PluginVerifyWebhook activity to the DefinitionSet method, which is appropriately placed before the existing PluginTranslateWebhook activity. This addition supports the new webhook verification flow described in the PR objectives.

internal/connectors/plugins/public/moneycorp/payments_test.go (2)

21-21: Change from concrete type to interface type is appropriate.

Changing plg from a pointer to the concrete Plugin struct to the models.Plugin interface aligns with the PR objective to use interfaces in tests. This improves type checking for function implementations and makes it easier to detect errors in plugin interface implementations.


222-222: Interface type declaration provides better type safety.

Consistent with the change in the first test block, this modification ensures the test is using the plugin interface rather than the concrete implementation. This approach helps validate that the plugin properly implements all required methods with the correct signatures.

internal/connectors/plugins/public/moneycorp/external_accounts_test.go (1)

19-19: Interface-based test setup ensures correct implementation.

Changing the test variable from a concrete *Plugin type to the models.Plugin interface type is consistent with the changes in other test files. This approach ensures that all methods required by the plugin interface are correctly implemented and type-checked at compile time.

internal/connectors/plugins/base_plugin.go (1)

79-81: Implementation of new webhook verification method.

The addition of the VerifyWebhook method to the base plugin supports the PR's goal of adding webhook idempotency key verification. This default implementation correctly returns ErrNotImplemented, consistent with the pattern used by other unimplemented methods in the base plugin. This allows each plugin to implement its own webhook verification logic while providing a standard interface.

internal/connectors/plugins/public/moneycorp/balances_test.go (1)

18-18: Interface type declaration enhances type safety.

Changing plg from a concrete type to the models.Plugin interface type is consistent with the same changes in other test files. This pattern ensures that the Moneycorp plugin correctly implements all interface methods with proper signatures, making type errors apparent at compile time rather than runtime.

internal/connectors/plugins/public/adyen/plugin.go (1)

117-123: LGTM - VerifyWebhook implementation follows established patterns

The implementation follows the consistent error-handling pattern used by other plugin methods, correctly checking if the client is initialized before proceeding with webhook verification.

internal/connectors/plugins/public/mangopay/plugin.go (1)

173-177: LGTM - No-op implementation with clear explanation

This implementation correctly returns an empty response with no verification, as explained in the comments. The approach aligns with Mangopay's requirements where webhook verification isn't needed.

internal/connectors/plugins/public/wise/profiles_test.go (1)

18-18: LGTM - Improved test with interface type

Good refactoring to use the interface type (models.Plugin) instead of the concrete type, allowing for better testing of the interface contract rather than implementation details.

Also applies to: 25-25

internal/connectors/plugins/public/wise/uninstall_test.go (1)

15-15: LGTM - Consistent test improvement

This change consistently applies the same pattern of using the interface type for testing as seen in other test files, promoting better interface-based testing practices.

Also applies to: 22-22

internal/connectors/plugins/public/moneycorp/accounts_test.go (1)

18-18: Good use of interface type for better abstraction

Changing plg from a concrete type to the interface type models.Plugin improves abstraction and matches production usage patterns where code is typically programmed against interfaces rather than implementations.

internal/connectors/plugins/public/moneycorp/transfers_test.go (2)

19-20: Good use of interface type for better abstraction

Changing plg from a concrete type to the interface type models.Plugin improves abstraction and makes the test code more maintainable.


24-26: Improved test setup with centralized mock initialization

Moving the mock controller and client creation to the outer BeforeEach block reduces redundant setup code and improves test efficiency by avoiding repeated mock creation.

internal/connectors/plugins/public/column/plugin_test.go (3)

24-24: Good use of interface type for better abstraction

Changing plg from a concrete type to the interface type models.Plugin improves abstraction and matches production usage patterns.


207-207: Consistent use of interface type in nested context

Using the interface type consistently throughout the test file improves code clarity and maintainability.


212-215: Improved plugin initialization with proper type handling

The refactored initialization properly handles the temporary concrete type for field validation before assigning to the interface variable, maintaining the same validation logic while improving abstraction.

internal/connectors/plugins/public/column/plugin.go (1)

196-201: Well-structured webhook verification method

The new VerifyWebhook method follows the established pattern in the codebase by checking client initialization before delegating to a private implementation method. This addition supports the PR's goal of improving webhook idempotency by adding a dedicated verification step that can generate an idempotency key.

This implementation:

  1. Checks if the client is initialized
  2. Delegates to a private method for the actual verification
  3. Maintains consistent error handling with other plugin methods
internal/connectors/plugins/public/wise/balances_test.go (2)

18-18: Good interface change.

Switching from concrete type to the models.Plugin interface improves type safety for the test and aligns with the PR goal of ensuring correct function overrides. This change helps detect type errors at compile time rather than runtime.


25-25: Improved test setup.

The plugin initialization is now more direct, removing the need for a separate SetClient call. This makes the test setup cleaner and more maintainable.

internal/connectors/plugins/public/stripe/balances_test.go (2)

19-20: Good interface adoption pattern.

Changing the plugin variable type to the interface models.Plugin follows the same pattern as in other plugin tests, improving consistency across the codebase and enabling better type checking.


24-26: Simplified test setup.

Centralizing the mock client creation and plugin instantiation in a single BeforeEach block makes the test structure cleaner and more maintainable.

internal/connectors/plugins/public/modulr/transfers_test.go (2)

19-20: Consistent interface-based testing.

This change follows the same interface-based testing pattern as the other plugin tests, improving type safety and consistency across the codebase.


24-26: Streamlined test initialization.

The initialization of mock and plugin in a single BeforeEach block simplifies the test setup while maintaining the test functionality.

internal/storage/webhooks.go (4)

18-18: Good implementation of persistent idempotency key.

Adding the IdempotencyKey field to the webhook struct implements the persistent storage approach described in the PR objectives. This addresses the issue where Temporal workflows aren't retained indefinitely, which could lead to duplicate processing of replayed webhooks.


67-67: Proper model mapping for idempotency key.

The IdempotencyKey field is correctly mapped between the model and storage structs.


78-78: Model to storage mapping is consistent.

The idempotency key field is consistently mapped in both directions between the model and storage structs, which ensures data integrity.


19-21:

✅ Verification successful

Consider adding a unique constraint.

The field reordering looks good. For proper idempotency enforcement, ensure there's a unique constraint on (connector_id, idempotency_key) at the database level.


🏁 Script executed:

#!/bin/bash
# Check if the migration includes a unique constraint for the idempotency key
# Look for migration files related to webhooks idempotency
fd -t f -e sql webhooks-idempotency -X cat {} | grep -i "unique\|constraint\|index"

Length of output: 184


Unique constraint confirmed
The migration adds a unique index on (connector_id, idempotency_key), satisfying the idempotency requirement. No further action needed.

  • Migration file defines:
    CREATE UNIQUE INDEX webhooks_unique_idempotency_key ON webhooks (connector_id, idempotency_key);
internal/connectors/plugins/public/bankingcircle/accounts_test.go (2)

18-19: Good abstraction using the models.Plugin interface.

Changing the concrete struct to the interface type makes tests more flexible and robust against interface changes.


22-25: Improved test setup pattern.

Nice refactoring to initialize the mock client and plugin instance only once in the outer BeforeEach block. This reduces redundancy and centralizes the test setup, making it more maintainable.

internal/connectors/plugins/public/atlar/bank_account_creation_test.go (2)

20-21: Good abstraction using the models.Plugin interface.

Using the interface type instead of the concrete implementation improves type checking during tests, which aligns with the PR objective to detect type errors in function overrides.


24-27: Improved test setup organization.

Centralizing the mock creation and plugin initialization in the top-level BeforeEach block eliminates redundant setup code and makes tests more maintainable.

internal/connectors/plugins/public/mangopay/payouts_test.go (2)

20-21: Good abstraction using the models.Plugin interface.

Using the interface type instead of the concrete struct implementation aligns with the PR objective to improve type checking in tests.


24-27: Improved test setup organization.

Centralizing mock creation and plugin initialization in the top-level BeforeEach follows a consistent pattern across plugin tests, improving maintainability.

internal/storage/migrations/migrations.go (2)

31-32: New migration for webhook idempotency key.

This embedding of the SQL file supports the webhook idempotency feature mentioned in the PR objectives.


219-229:

✅ Verification successful

Well-structured migration implementation.

The migration follows the established pattern with proper transaction handling and logging. This addresses the PR objective to shift webhook idempotency checks from Temporal to persistent storage.

However, I'd recommend verifying that the migration includes a proper index on the idempotency_key column for performance, since lookups will happen frequently during webhook processing.


🏁 Script executed:

#!/bin/bash
# Examine the SQL migration content to verify the index creation
cat internal/storage/migrations/16-webhooks-idempotency-key.sql

Length of output: 235


Index creation verified for webhook idempotency migration

The SQL migration not only adds the idempotency_key column but also creates a unique index (webhooks_unique_idempotency_key) on (connector_id, idempotency_key), ensuring efficient lookups. This addresses the performance concern—LGTM.

internal/connectors/plugins/public/currencycloud/external_accounts_test.go (1)

18-19: Good refactoring to use interface instead of concrete type

The change from concrete type to interface (models.Plugin instead of *Plugin) aligns with the PR objective to ensure better type checking in function overrides. This will help catch signature mismatches at compile time rather than runtime.

The centralized mock setup in the top-level BeforeEach also improves test maintenance by eliminating duplicate mock creation code.

Also applies to: 23-25, 35-35

internal/connectors/plugins/public/modulr/external_accounts_test.go (1)

18-19: Good refactoring to use interface instead of concrete type

The change from concrete type to interface (models.Plugin instead of *Plugin) properly implements the PR objective to use interfaces for better type checking. The centralized mock setup pattern is consistent with the changes in other plugin test files.

Also applies to: 23-25

internal/storage/webhooks_test.go (4)

8-8: LGTM: Added pointer package import for idempotency key tests

The pointer package import is necessary for creating string pointer values in the test data.


28-30: LGTM: Updated test webhook data with idempotency key

Good update to include an idempotency key in the test data to support testing the new functionality.


83-98: LGTM: Good test coverage for null idempotency keys

This test case verifies that multiple webhooks with null idempotency keys can be stored without conflicts, which is important behavior to validate.


100-117: LGTM: Good test coverage for duplicate idempotency keys

This test case properly verifies that the database enforces uniqueness on the idempotency key column, which is a critical aspect of the webhook idempotency implementation described in the PR objectives.

internal/connectors/plugins/registry/wrapper.go (1)

298-314: LGTM: Well-implemented VerifyWebhook method with proper tracing and error handling

The new VerifyWebhook method correctly follows the established pattern for plugin method wrappers:

  1. Creates and manages OpenTelemetry spans with appropriate attributes
  2. Provides consistent logging before and after the operation
  3. Properly handles and translates errors
  4. Returns the plugin's response

This implementation supports the PR objective of adding webhook verification that generates idempotency keys to prevent duplicate processing.

internal/connectors/plugins/public/wise/transfers_test.go (1)

20-27: Using plugin interface over concrete implementation improves test robustness.

The changes refactor the test setup to use the models.Plugin interface type instead of the concrete implementation, which aligns with the PR objective to make it easier to catch type errors in function overrides. The mock setup is now centralized in the top-level BeforeEach block.

internal/connectors/plugins/public/modulr/balances_test.go (1)

16-24: Clean refactoring to use plugin interface and centralize mock setup.

This change appropriately moves the mock client initialization and plugin creation to the top-level BeforeEach block and changes from using a concrete Plugin instance to the models.Plugin interface. This matches the PR goal of ensuring proper interface compliance and improving type checking.

internal/connectors/plugins/public/adyen/accounts_test.go (1)

18-27: Improved test structure using plugin interface for type safety.

The test setup has been refactored to use the models.Plugin interface rather than the concrete Plugin struct, and mock initialization has been centralized in the top-level BeforeEach. This change ensures proper type checking of interface implementations and is consistent with changes across other plugin tests.

internal/connectors/engine/workflow/handle_webhooks.go (2)

48-65: Added webhook verification step for improved idempotency handling.

This change implements the new webhook verification mechanism that shifts idempotency enforcement from Temporal workflows to persistent storage. The workflow now calls the PluginVerifyWebhook activity to verify the webhook and obtain an idempotency key, which is then stored with the webhook in the database.


94-94: Updated workflow ID generation to use webhook ID.

The workflow ID for storing webhook translations now uses the webhook's ID instead of the idempotency key, which aligns with the shift from Temporal-based to storage-based idempotency control.

internal/connectors/plugins/public/bankingcircle/payouts_test.go (1)

19-20: Improved test setup pattern using interface types.

The test refactoring now properly uses the models.Plugin interface type instead of the concrete Plugin struct, which aligns with the PR's goal of ensuring type-checking for plugin interface implementations. This change helps detect type errors in function overrides that might be missed when using the concrete type.

Also applies to: 24-26

internal/connectors/plugins/public/bankingcircle/balances_test.go (1)

15-16: LGTM: Consistent test setup pattern.

This change follows the same improved pattern of using models.Plugin interface instead of concrete types. The simplified test setup with a centralized mock client creation enhances maintainability.

Also applies to: 20-22

internal/connectors/plugins/public/atlar/payouts_test.go (1)

24-25: Consistent interface-based testing approach.

Good refactoring to use the models.Plugin interface type in tests, consistent with other plugin test changes. This ensures the test validates that the plugin correctly implements all interface methods with proper type signatures.

Also applies to: 29-31

internal/connectors/plugins/public/atlar/payments_test.go (1)

23-24: Clean interface-based test refactoring.

This change aligns with the PR objective of using interface types in tests to ensure proper type checking. The mock client is now created once per test run and injected consistently.

Also applies to: 28-30

internal/connectors/plugins/public/stripe/accounts_test.go (2)

17-19: Good refactoring to use the models.Plugin interface

This change correctly implements the interface-based approach by declaring plg as models.Plugin rather than using the concrete struct type. This ensures proper type-checking of all implemented interface methods.


21-25: Improved test setup organization

Good job centralizing the mock client initialization and plugin instantiation in the outer BeforeEach block. This removes redundant setup code in nested contexts and makes the test structure cleaner and more maintainable.

internal/connectors/plugins/public/mangopay/external_accounts_test.go (2)

18-19: Good refactoring to use the models.Plugin interface

Correctly declaring plg as models.Plugin rather than a concrete struct type ensures proper interface compliance and type-checking of all implemented methods, which aligns with the PR objective to improve plugin test accuracy.


22-26: Improved test structure with centralized mock initialization

Centralizing the mock client creation and plugin instantiation in the top-level BeforeEach block is a good practice. This removes redundant setup code and ensures consistent mock usage across all test cases.

internal/connectors/plugins/public/mangopay/balances_test.go (2)

17-19: Good refactoring to use the models.Plugin interface

Using the models.Plugin interface type rather than a concrete struct ensures proper type-checking of implemented methods, which helps catch potential type errors in function overrides.


21-25: Improved test setup organization

Good job centralizing the mock client initialization and plugin instantiation in the outer BeforeEach block. This eliminates redundant setup code and ensures consistent use of the mock client across tests.

internal/connectors/plugins/public/currencycloud/payouts_test.go (2)

19-20: Good refactoring to use the models.Plugin interface

Correctly changing the plugin type from a concrete struct to the models.Plugin interface ensures proper type-checking of all implemented interface methods, making the tests more robust at detecting function signature mismatches.


23-27: Improved test structure with centralized mock initialization

Centralizing the mock client initialization and plugin instantiation in the top-level BeforeEach block removes duplicate code and creates a more maintainable test structure. This is consistent with the refactoring pattern applied across all plugin test suites.

internal/connectors/plugins/public/bankingcircle/payments_test.go (1)

18-20: Good refactoring to use interface typing and centralized mock setup.

This change improves the test by:

  1. Using models.Plugin interface type instead of a concrete struct type
  2. Creating the mock client once in the outer BeforeEach
  3. Properly injecting the mock into the plugin instance

This will ensure type checking at compile time for all interface methods and reduces redundant mock initialization in nested BeforeEach blocks.

Also applies to: 23-25

internal/connectors/plugins/public/column/payments_test.go (1)

19-20: Good refactoring to use interface typing and centralized mock setup.

The test has been improved by:

  1. Using models.Plugin interface type instead of a concrete struct type
  2. Creating the mock HTTP client once in the outer BeforeEach
  3. Properly initializing the client with the mock and injecting it into the plugin

This ensures proper type checking of interface methods and reduces redundant initialization code in nested contexts.

Also applies to: 24-28

internal/connectors/plugins/public/modulr/payments_test.go (2)

20-21: Good refactoring to use interface typing and centralized mock setup.

This change improves the test by:

  1. Using models.Plugin interface type instead of a concrete struct type
  2. Creating the mock client once in the outer BeforeEach
  3. Properly injecting the mock into the plugin instance

This will ensure type checking at compile time for all interface methods and reduces redundant mock initialization in nested BeforeEach blocks.

Also applies to: 25-27


210-210: Corrected context description.

Fixed the context description from "fetching next accounts" to "transaction to payments" to accurately reflect what the tests are verifying.

internal/connectors/plugins/public/column/balances_test.go (1)

16-17: Good refactoring to use interface typing and centralized mock setup.

The test has been improved by:

  1. Using models.Plugin interface type instead of a concrete struct type
  2. Creating the mock HTTP client once in the outer BeforeEach
  3. Properly initializing the client with the mock and injecting it into the plugin

This ensures proper type checking of interface methods and reduces redundant initialization code in nested contexts.

Also applies to: 21-25

internal/connectors/plugins/public/wise/payouts_test.go (2)

20-21: Refactored test variables to use interface type instead of concrete implementation

The test now declares variables at the top level with plg typed as models.Plugin instead of a concrete *Plugin type, improving type safety by ensuring tests work against the interface.


25-27: Improved test setup with centralized mock initialization

The test now initializes the mock client and plugin instance once in the top-level BeforeEach, which reduces duplication and follows best practices for test organization. This change is part of the PR objective to use the interface type for plugins in tests to ensure function overrides are correctly type-checked.

internal/connectors/plugins/public/bankingcircle/bank_account_creation_test.go (2)

18-19: Refactored test variables to use interface type instead of concrete implementation

The test now declares variables at the top level with plg typed as models.Plugin instead of a concrete *Plugin type, improving type safety by ensuring tests work against the interface.


23-25: Improved test setup with centralized mock initialization

The test now initializes the mock client and plugin instance once in the top-level BeforeEach, which reduces duplication and follows best practices for test organization. This change is part of the PR objective to use the interface type for plugins in tests to ensure function overrides are correctly type-checked.

internal/connectors/plugins/public/modulr/accounts_test.go (2)

18-19: Refactored test variables to use interface type instead of concrete implementation

The test now declares variables at the top level with plg typed as models.Plugin instead of a concrete *Plugin type, improving type safety by ensuring tests work against the interface.


23-25: Improved test setup with centralized mock initialization

The test now initializes the mock client and plugin instance once in the top-level BeforeEach, which reduces duplication and follows best practices for test organization. This change is part of the PR objective to use the interface type for plugins in tests to ensure function overrides are correctly type-checked.

internal/connectors/plugins/public/stripe/reverse_transfers_test.go (2)

21-22: Refactored test variables to use interface type instead of concrete implementation

The test now declares variables at the top level with plg typed as models.Plugin instead of a concrete *Plugin type, improving type safety by ensuring tests work against the interface.


25-27: Improved test setup with centralized mock initialization

The test now initializes the mock client and plugin instance once in the top-level BeforeEach, which reduces duplication and follows best practices for test organization. This change is part of the PR objective to use the interface type for plugins in tests to ensure function overrides are correctly type-checked.

internal/connectors/plugins/public/mangopay/transfers_test.go (2)

20-21: Good use of the interface type for plugin variable.

Changing from a concrete type to the interface type (models.Plugin) aligns with the PR objective of improving type checking for plugin tests.


25-27: Improved test structure with centralized mock setup.

Centralizing the mock client creation and plugin instantiation in the top-level BeforeEach is a good practice. This eliminates redundant setup code and makes the tests more maintainable.

internal/connectors/plugins/public/currencycloud/balances_test.go (2)

16-17: Good use of the interface type for plugin variable.

Changing from a concrete type to the interface type (models.Plugin) aligns with the PR objective of improving type checking for plugin tests.


21-23: Improved test structure with centralized mock setup.

Centralizing the mock client creation and plugin instantiation in the top-level BeforeEach is a good practice. This eliminates redundant setup code and makes the tests more maintainable.

internal/connectors/plugins/public/generic/accounts_test.go (2)

19-20: Good use of the interface type for plugin variable.

Changing from a concrete type to the interface type (models.Plugin) aligns with the PR objective of improving type checking for plugin tests.


24-26: Improved test structure with centralized mock setup.

Centralizing the mock client creation and plugin instantiation in the top-level BeforeEach is a good practice. This eliminates redundant setup code and makes the tests more maintainable.

internal/connectors/plugins/public/bankingcircle/transfers_test.go (3)

19-20: Good use of the interface type for plugin variable.

Changing from a concrete type to the interface type (models.Plugin) aligns with the PR objective of improving type checking for plugin tests.


24-26: Improved test structure with centralized mock setup.

Centralizing the mock client creation and plugin instantiation in the top-level BeforeEach is a good practice. This eliminates redundant setup code and makes the tests more maintainable.


36-36: Minor spacing change.

This empty line addition is just a formatting change with no functional impact.

internal/models/plugin.go (2)

39-39: Good addition of the VerifyWebhook method to the interface.

This method will enable plugins to verify webhooks and return idempotency keys, supporting the shift from Temporal-based to database-based webhook idempotency tracking.


158-165: LGTM: Well-structured request and response types for webhook verification.

The request includes all necessary components (webhook payload and optional config), and the response includes an optional idempotency key pointer which allows plugins to:

  1. Return a PSP-generated event ID
  2. Generate a hash from the payload
  3. Return nil when idempotency isn't needed

This design provides good flexibility for different plugin implementations.

internal/connectors/plugins/public/wise/payments_test.go (2)

19-20: Good refactoring of test variables.

Changing plg from concrete type to interface type models.Plugin is a good improvement that helps enforce the interface contract and will catch type errors in function implementations.


23-26: Improved test setup organization.

Centralizing the mock client creation and plugin initialization in the top-level BeforeEach improves test maintainability and avoids redundant setup code in nested test contexts.

internal/connectors/plugins/public/mangopay/bank_account_creation_test.go (2)

19-20: Good refactoring of test variables.

Changing plg from concrete type to interface type models.Plugin helps enforce the interface contract and will catch type errors in function implementations.


23-26: Improved test setup organization.

Centralizing the mock client creation and plugin initialization in the top-level BeforeEach improves test maintainability and avoids redundant setup code in nested test contexts.

internal/connectors/plugins/public/mangopay/payments_test.go (2)

18-20: Good refactoring of test variables.

Changing plg from concrete type to interface type models.Plugin helps enforce the interface contract and will catch type errors in function implementations.


22-26: Improved test setup organization.

Centralizing the mock client creation and plugin initialization in the top-level BeforeEach improves test maintainability and avoids redundant setup code in nested test contexts.

internal/connectors/plugins/public/atlar/accounts_test.go (1)

22-24: Good refactoring to use the plugin interface type

Using models.Plugin instead of the concrete struct type improves type safety by ensuring that all interface methods are properly implemented. This change helps catch type errors in function overrides at compile time, aligning with the PR objective.

Also applies to: 27-29

internal/connectors/plugins/public/generic/balances_test.go (1)

18-19: Improved test structure with interface-based testing

Good refactoring to use the models.Plugin interface instead of concrete struct types. This approach ensures proper implementation of the interface methods and aligns with the PR objective to make it easier to detect type errors in function overrides.

Also applies to: 23-25

internal/connectors/plugins/public/stripe/external_accounts_test.go (1)

17-18: Well-structured interface-based testing approach

The change from concrete struct to models.Plugin interface type improves type safety and test reliability. This refactoring centralizes mock setup and ensures proper interface implementation, which helps catch potential issues earlier in the development process.

Also applies to: 22-24

internal/connectors/plugins/public/wise/external_accounts_test.go (1)

18-19: Good consistent refactoring to use interface type

This change follows the same pattern as the other plugin test refactorings, using the models.Plugin interface type instead of concrete struct. This approach ensures proper interface implementation and type checking, aligning with the PR objective to improve plugin testing.

Also applies to: 23-25

internal/connectors/plugins/public/wise/accounts_ 10000 test.go (1)

18-19: Good refactoring to use interface type instead of concrete struct.

The change from concrete type to models.Plugin interface improves type safety by ensuring the plugin correctly implements all interface methods. Centralizing the mock client creation in the top-level BeforeEach eliminates redundant initialization code that was likely present in nested contexts before.

Also applies to: 23-25

internal/connectors/plugins/public/stripe/create_payouts_test.go (1)

21-22: Good refactoring to use interface type instead of concrete struct.

The change from concrete type to models.Plugin interface improves type safety by ensuring the plugin correctly implements all interface methods. Centralizing the mock client creation in the top-level BeforeEach eliminates redundant initialization code.

Also applies to: 26-28

internal/connectors/plugins/public/atlar/balances_test.go (1)

20-21: Good refactoring to use interface type instead of concrete struct.

The change from concrete type to models.Plugin interface improves type safety and consistency. This ensures that the plugin correctly implements all required interface methods, which addresses one of the PR objectives.

Also applies to: 25-27

internal/connectors/plugins/public/mangopay/users_test.go (1)

18-19: Good refactoring to use interface type instead of concrete struct.

The change from concrete type to models.Plugin interface ensures proper type checking for function overrides. This directly addresses one of the PR objectives to make it easier to detect type errors in function overrides.

Also applies to: 23-25

internal/connectors/plugins/public/currencycloud/transfers_test.go (2)

19-21: Approved: Using the interface type improves type checking

The change from concrete type to the models.Plugin interface allows the compiler to verify that all interface methods are correctly implemented, which aligns with the PR objective of improving type checking.


24-27: Good refactoring of test setup

Moving the mock client creation and plugin initialization to the top-level BeforeEach centralizes the setup code and removes potential duplication in nested contexts. This improves test maintainability.

internal/connectors/engine/activities/plugin_verify_webhook_test.go (2)

21-86: Well-structured test suite for the new webhook verification functionality

This test suite thoroughly covers the new PluginVerifyWebhook activity with appropriate test cases:

  1. Success case that verifies the activity correctly forwards webhook verification to the plugin
  2. Error handling for general plugin errors (retryable)
  3. Error handling for "not implemented" errors (non-retryable)

This aligns perfectly with the PR objectives of implementing webhook idempotency via the new VerifyWebhook plugin function.


57-62: Verify correct handling of the webhook idempotency key

The test correctly verifies that the idempotency key from the plugin's response is passed through by the activity.

internal/connectors/plugins/public/generic/payments_test.go (2)

20-21: Improved type checking with interface type

Changing from the concrete *Plugin type to the models.Plugin interface ensures that the compiler verifies all interface methods are correctly implemented, supporting the PR's goal of better type checking in plugin tests.


24-28: Centralized mock setup improves test clarity

Moving the mock creation and plugin initialization to the top-level BeforeEach follows a consistent pattern applied across multiple plugin tests, reducing code duplication and improving maintainability.

internal/connectors/plugins/public/column/accounts_test.go (4)

18-20: Improved type checking with interface type

Changing from concrete type to the models.Plugin interface ensures that the compiler verifies all interface methods are correctly implemented, supporting the PR's goal of better type checking.


22-28: Well-structured test setup

The test setup now properly initializes the mock HTTP client and injects it into the plugin, providing a consistent pattern that matches other plugin tests in this PR.


49-68: More realistic HTTP error testing

The test now explicitly mocks the HTTP status code (500) and error return, providing a more accurate representation of how the client would behave in error scenarios. The error expectation has been updated to match the new error handling path.


69-98: Improved timestamp parsing error test

This test now correctly mocks a successful HTTP response but with invalid timestamp data, which is a more realistic test scenario. The test verifies that the plugin properly handles and propagates parsing errors from invalid response data.

internal/connectors/plugins/public/mangopay/accounts_test.go (2)

18-19: Improved testability by using interface type instead of concrete struct.

Changed variable declaration from concrete plugin type to a more testable approach with separate mock client and plugin interface variables.


23-25: Well-structured test setup with centralized mock initialization.

Good refactoring to initialize the mock controller and client once in the outer BeforeEach block, and injecting the mock into the plugin instance. This eliminates duplicate initialization in nested test blocks and improves test maintenance.

internal/connectors/plugins/public/atlar/external_accounts_test.go (2)

22-23: Improved testability by using interface type instead of concrete struct.

Changed variable declaration from concrete plugin type to a more testable approach with separate mock client and plugin interface variables.


27-29: Well-structured test setup with centralized mock initialization.

Good refactoring to initialize the mock controller and client once in the outer BeforeEach block, and injecting the mock into the plugin instance. This eliminates duplicate initialization in nested test blocks and improves test maintenance.

internal/connectors/plugins/public/moneycorp/payouts_test.go (2)

19-20: Improved testability by using interface type instead of concrete struct.

Changed variable declaration from concrete plugin type to a more testable approach with separate mock client and plugin interface variables.


24-26: Well-structured test setup with centralized mock initialization.

Good refactoring to initialize the mock controller and client once in the outer BeforeEach block, and injecting the mock into the plugin instance. This eliminates duplicate initialization in nested test blocks and improves test maintenance.

internal/connectors/plugins/public/generic/external_accounts_test.go (2)

19-20: Improved testability by using interface type instead of concrete struct.

Changed variable declaration from concrete plugin type to a more testable approach with separate mock client and plugin interface variables.


24-26: Well-structured test setup with centralized mock initialization.

Good refactoring to initialize the mock controller and client once in the outer BeforeEach block, and injecting the mock into the plugin instance. This eliminates duplicate initialization in nested test blocks and improves test maintenance.

internal/connectors/plugins/public/stripe/create_transfers_test.go (1)

21-29: Improved test structure using interface types

Good refactoring to use the models.Plugin interface instead of the concrete struct type. This change ensures proper type checking of the plugin interface implementation and centralizes the mock client setup in the top-level BeforeEach, reducing duplication and improving maintainability.

internal/connectors/plugins/public/modulr/payouts_test.go (1)

19-27: Improved test structure using interface types

This refactoring correctly uses the models.Plugin interface instead of the concrete struct type, which ensures proper type checking of the plugin interface implementation. Centralizing the mock client setup in the top-level BeforeEach is a good practice that reduces duplication.

internal/connectors/plugins/public/currencycloud/payments_test.go (2)

22-30: Improved test structure using interface types

Good refactoring to use the models.Plugin interface instead of the concrete struct type. This ensures proper type checking of the plugin interface implementation and centralizes the mock client setup, reducing duplication in nested test contexts.


39-54: Great handling of samples creation in test setup

The test setup for sample transactions is well structured. Creating a loop to generate 50 sample transactions with incrementing values makes the test data more realistic and provides good coverage for pagination testing.

internal/connectors/plugins/public/currencycloud/accounts_test.go (2)

18-19: Good standardization of test variables.

Refactoring the mock client and plugin declarations to the top-level test suite scope improves test organization and eliminates duplicated setup code. Using the models.Plugin interface instead of the concrete type helps ensure proper interface adherence.


23-26: Good centralized setup pattern.

Centralizing the mock controller, mock client, and plugin initialization in the top-level BeforeEach improves test maintainability and reduces code duplication. The initialization pattern ensures consistent test setup across all test contexts.

internal/connectors/plugins/public/column/transfers_test.go (4)

18-19: Good standardization of test variables.

Declaring the mock HTTP client and plugin at the test suite level using the models.Plugin interface type improves test organization and ensures proper interface adherence.


23-27: Good centralized setup pattern.

Centralizing the mock controller creation, HTTP client initialization, and plugin setup ensures consistent test environment across all test cases, reducing duplication and improving maintainability.


33-40: Improved test approach using public API.

Converting the tests to use the public CreateTransfer method instead of the internal validateTransferRequest method is a significant improvement. This ensures that tests validate the actual API contract that clients will use rather than internal implementation details.

Also applies to: 44-57, 61-81, 85-92, 96-104


345-352: Good test isolation pattern.

Creating a local plugin variable for specific test contexts helps isolate test conditions. Using an invalid URL to test error handling is a good approach to verify error propagation from HTTP client creation failures.

internal/connectors/plugins/public/column/external_accounts_test.go (2)

18-19: Good standardization of test variables.

Declaring the mock HTTP client and plugin at the test suite level using the models.Plugin interface type improves test organization and ensures proper interface adherence.


23-27: Good centralized setup pattern.

Centralizing the mock controller creation, HTTP client initialization, and plugin setup in the top-level BeforeEach ensures consistent test setup across all test cases. This reduces code duplication and improves test maintainability.

internal/models/plugin_generated.go (2)

297-310: Good implementation of mock for new VerifyWebhook method.

The implementation of the VerifyWebhook mock method in MockPlugin follows the established pattern for mock methods in this file. This supports the new webhook verification functionality being added to the plugin interface.


546-559: Good implementation of mock for new VerifyWebhook method in PSPPlugin.

The implementation of the VerifyWebhook mock method in MockPSPPlugin properly mirrors the implementation in MockPlugin, maintaining consistency across the mock implementations. This supports the webhook idempotency key mechanism being introduced in this PR.

internal/connectors/plugins/public/column/webhook.go (1)

330-359: Clean separation of translation from verification

The updated translateWebhook method now focuses solely on retrieving the appropriate webhook translator and translating the event. This is a good separation of concerns from the verification logic, making the code more maintainable and focused.

internal/connectors/engine/activities/plugin_verify_webhook.go (2)

10-13: Well-structured request with clear separation of concerns

The VerifyWebhookRequest struct properly encapsulates the connector ID and verification request data needed for the activity.


15-26: Good error handling for plugin verification activity

The PluginVerifyWebhook method properly retrieves the plugin by connector ID and delegates to the plugin's VerifyWebhook method, with appropriate error wrapping.

internal/connectors/engine/workflow/handle_webhooks_test.go (6)

26-34: Good test coverage for idempotency key propagation

The test correctly mocks the PluginVerifyWebhookActivity to return an idempotency key and verifies that it's correctly propagated to the webhook storage.


89-97: Consistent testing approach for no-responses case

This test also properly checks idempotency key propagation, maintaining consistency with the success test.


180-215: Good error case testing for webhook verification

The test appropriately verifies that errors from the webhook verification activity are propagated to the workflow.


228-229: Consistent mocking for StorageWebhooksStore test

The test case has been correctly updated to include the verification activity.


265-266: Consistent mocking in TranslateWebhook test

The test case has been properly updated to include the verification activity.


303-304: Consistent mocking in RunStoreWebhookTranslation test

The test case has been properly updated to include the verification activity.

internal/connectors/plugins/public/mangopay/webhooks_test.go (4)

20-23: Good refactoring to use interface type

Changing from concrete *Plugin type to models.Plugin interface makes the tests more robust by detecting potential type errors in implementations.


24-30: Improved test setup with centralized mock configuration

Centralizing mock client creation and plugin initialization in the top-level BeforeEach block reduces duplication and improves test maintainability.


215-215: Updated assertion for length check

The code now correctly uses the local plugin instance to verify the length of returned webhook configs.


226-232: Consistent test setup pattern

The mock setup follows the same pattern as the previous test context, centralizing client mock creation and plugin initialization.

internal/connectors/plugins/public/wise/plugin.go (2)

175-205: Well-structured webhook verification implementation.

The new VerifyWebhook method properly separates verification logic from translation, following the single responsibility principle. The method correctly validates headers and verifies signatures before returning an idempotency key derived from the X-Delivery-Id header.

This implementation aligns with the PR objective of shifting idempotency checks to persistent storage by returning a unique identifier from the webhook provider.


207-232: Clean separation of translation concerns.

The TranslateWebhook method has been properly refactored to focus solely on translating webhook payloads, removing signature verification and idempotency key assignment that are now handled in the new VerifyWebhook method.

This separation of concerns improves maintainability and allows for more robust idempotency handling through persistent storage.

internal/connectors/plugins/public/column/reverse_payout_test.go (3)

17-27: Good switch to interface-based testing.

Changing the plg variable from a concrete Plugin type to the models.Plugin interface improves test maintainability and follows good testing practices by programming to interfaces rather than implementations.

The centralized mock setup in the top-level BeforeEach also reduces duplication and makes the tests more maintainable.


31-40: Improved test methodology using public API.

The test now correctly uses the public ReversePayout method with a proper ReversePayoutRequest struct instead of calling internal validation methods directly. This better reflects how the code will be used in production and ensures the complete call path is tested.


114-123: Better test structure with proper initialization.

The HTTP Request Creation Errors test context now follows the same initialization pattern as other tests, improving consistency across the test suite.

internal/connectors/plugins/public/column/payouts_test.go (2)

16-26: Good switch to interface-based testing with centralized setup.

Changing the plg variable to the models.Plugin interface type and centralizing the mock setup in the top-level BeforeEach improves test maintainability and follows the dependency inversion principle.


29-36: Improved test methodology using public API.

The validation tests now correctly use the public CreatePayout method with a proper request struct instead of calling internal validation methods directly. This ensures the tests verify the behavior as experienced by consumers of the API.

internal/connectors/plugins/public/adyen/webhooks_test.go (3)

21-32: Good switch to interface-based testing with proper initialization.

Changing plg to the models.Plugin interface type and properly initializing the plugin improves test maintainability and follows good testing practices.


70-168: Well-structured tests for the new verification functionality.

The new "verifying webhooks" test context thoroughly tests the webhook verification functionality, including basic auth failure, invalid HMAC, and successful verification scenarios.

The successful verification test correctly ensures that a SHA-256 hash of the webhook body is returned as the idempotency key, which aligns with the PR objective of implementing robust idempotency checking.


461-509: Clean helper function refactoring.

The doTranslateCall helper function has been properly updated to work with the interface type and now focuses solely on testing translation, as verification is tested separately. This maintains the separation of concerns in both the implementation and tests.

Copy link
codecov bot commented May 14, 2025

Codecov Report

Attention: Patch coverage is 50.00000% with 86 lines in your changes missing coverage. Please review.

Project coverage is 68.42%. Comparing base (296ec3f) to head (e1820d1).
Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
internal/models/plugin_generated.go 0.00% 18 Missing ⚠️
internal/connectors/plugins/registry/wrapper.go 0.00% 15 Missing ⚠️
...nectors/engine/activities/plugin_verify_webhook.go 36.84% 11 Missing and 1 partial ⚠️
internal/connectors/plugins/public/wise/plugin.go 42.10% 9 Missing and 2 partials ⚠️
...ternal/connectors/plugins/public/column/webhook.go 61.90% 5 Missing and 3 partials ⚠️
internal/connectors/engine/activities/activity.go 0.00% 4 Missing ⚠️
...ternal/connectors/plugins/public/adyen/webhooks.go 76.47% 3 Missing and 1 partial ⚠️
...ernal/connectors/plugins/public/mangopay/plugin.go 0.00% 4 Missing ⚠️
internal/connectors/plugins/public/adyen/plugin.go 40.00% 2 Missing and 1 partial ⚠️
...nternal/connectors/plugins/public/column/plugin.go 40.00% 2 Missing and 1 partial ⚠️
... and 2 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #445      +/-   ##
==========================================
+ Coverage   68.40%   68.42%   +0.01%     
==========================================
  Files         582      586       +4     
  Lines       29583    29805     +222     
==========================================
+ Hits        20237    20394     +157     
- Misses       8272     8328      +56     
- Partials     1074     1083       +9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor
@laouji laouji left a comment

Choose a reason for hiding this comment

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

Looks like you had fun fixing tests 😅

@paul-nicolas paul-nicolas force-pushed the feat/webhooks-idempotency-key branch from 292cdcb to 1791068 Compare May 14, 2025 14:36
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: 1

🔭 Outside diff range comments (2)
internal/connectors/plugins/public/wise/plugin_test.go (1)

34-34: 💡 Verification agent

🧩 Analysis chain

Consider using models.Plugin interface instead of concrete Plugin struct

The PR objective mentions changing tests to use the models.Plugin interface instead of the concrete struct to improve type checking in function overrides. However, you're still using the concrete *Plugin type here. Consider:

- var plg *Plugin
+ var plg models.Plugin

This change would help catch type errors in plugin interface implementations during tests.


🏁 Script executed:

#!/bin/bash
# Verify if there are other test files that have been changed to use models.Plugin

echo "Checking other test files for similar changes..."
rg -t go "var\s+(\w+).*models\.Plugin" --files-with-matches internal/connectors/plugins/

Length of output: 987


🏁 Script executed:

#!/bin/bash
echo "Listing plugin_test.go files:"
find internal/connectors/plugins -type f -name "plugin_test.go"

echo
echo "Column plugin_test.go snippet:"
sed -n '1,200p' internal/connectors/plugins/public/column/plugin_test.go

echo
echo "Wise plugin_test.go snippet:"
sed -n '1,200p' internal/connectors/plugins/public/wise/plugin_test.go

echo
echo "Looking for 'var plg' in test files:"
rg -n "var plg" internal/connectors/plugins -t go | grep plugin_test.go

Length of output: 13174


Use the models.Plugin interface in the Wise plugin test

To keep consistency with other connector tests (e.g. Column) and to catch interface-level type errors, declare plg as the models.Plugin interface rather than the concrete *Plugin struct.

• File: internal/connectors/plugins/public/wise/plugin_test.go
Change in the var block at the top of the Describe("Wise Plugin", …) suite:

 var (
-   plg        *Plugin
+   plg        models.Plugin
    block      *pem.Block
    pemKey     *bytes.Buffer
    privatekey *rsa.PrivateKey
    logger     = logging.NewDefaultLogger(GinkgoWriter, true, false, false)
 )
internal/connectors/plugins/public/stripe/payments_test.go (1)

19-20: 💡 Verification agent

🧩 Analysis chain

Plugin variable still uses concrete type

The PR objective mentioned changing tests to use the models.Plugin interface instead of concrete structs, but plg is still typed as *Plugin (concrete type) rather than models.Plugin (interface).


🏁 Script executed:

#!/bin/bash
# Check if other test files in this PR have been changed to use the models.Plugin interface
rg -A 1 -B 1 "var \(\s*\w+\s+models\.Plugin" --type go

Length of output: 56


I’ll pull in the import context from the test and locate the models.Plugin interface so we can confirm the correct import path and update.


🏁 Script executed:

#!/bin/bash
# Show imports and the `plg` declaration in payments_test.go
echo "=== payments_test.g
3D11
o (lines 1–100) ==="
sed -n '1,100p' internal/connectors/plugins/public/stripe/payments_test.go

echo
# Find where models.Plugin is defined to get its package path
echo "=== models.Plugin interface definition ==="
rg "type Plugin interface" -n --type go

Length of output: 3537


Use the models.Plugin interface for plg instead of the concrete *Plugin

To align with the PR’s objective of depending on the models.Plugin interface, update the declaration in payments_test.go:

• File: internal/connectors/plugins/public/stripe/payments_test.go
Change in the top-level var block:

-   plg    *Plugin
+   plg    models.Plugin

This lets you assign the concrete &Plugin{…} in BeforeEach while keeping tests focused on the interface.

🧹 Nitpick comments (1)
internal/connectors/plugins/public/wise/plugin_test.go (1)

157-172: Consider testing the transfer_state_changed webhook verification

You currently only test verification for "balance_update" webhooks. Consider adding similar tests for "transfer_state_changed" webhooks to ensure all supported webhook types are verified correctly.

+ It("it can create the transfer_state_changed webhook", func(ctx SpecContext) {
+   req := models.VerifyWebhookRequest{
+     Config: &models.WebhookConfig{Name: "transfer_state_changed"},
+     Webhook: models.PSPWebhook{
+       Body: body,
+       Headers: map[string][]string{
+         HeadersDeliveryID: {"different-delivery-id"},
+         HeadersSignature:  {base64.StdEncoding.EncodeToString(signature)},
+       },
+     },
+   }
+   
+   res, err := plg.VerifyWebhook(ctx, req)
+   Expect(err).To(BeNil())
+   Expect(res.WebhookIdempotencyKey).To(Equal(&req.Webhook.Headers[HeadersDeliveryID][0]))
+ })
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b772cd3 and 1791068.

📒 Files selected for processing (76)
  • internal/connectors/plugins/public/adyen/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/adyen/plugin_test.go (2 hunks)
  • internal/connectors/plugins/public/adyen/webhooks.go (3 hunks)
  • internal/connectors/plugins/public/adyen/webhooks_test.go (7 hunks)
  • internal/connectors/plugins/public/atlar/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/atlar/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/atlar/bank_account_creation_test.go (1 hunks)
  • internal/connectors/plugins/public/atlar/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/atlar/payments_test.go (1 hunks)
  • internal/connectors/plugins/public/atlar/payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/bankingcircle/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/bankingcircle/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/bankingcircle/bank_account_creation_test.go (1 hunks)
  • internal/connectors/plugins/public/bankingcircle/payments_test.go (1 hunks)
  • internal/connectors/plugins/public/bankingcircle/payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/bankingcircle/transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/column/accounts_test.go (3 hunks)
  • internal/connectors/plugins/public/column/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/column/bank_account_creation_test.go (1 hunks)
  • internal/connectors/plugins/public/column/client/error.go (1 hunks)
  • internal/connectors/plugins/public/column/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/column/payments_test.go (1 hunks)
  • internal/connectors/plugins/public/column/payouts_test.go (5 hunks)
  • internal/connectors/plugins/public/column/reverse_payout_test.go (2 hunks)
  • internal/connectors/plugins/public/column/transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/column/uninstall_test.go (5 hunks)
  • internal/connectors/plugins/public/column/webhook.go (1 hunks)
  • internal/connectors/plugins/public/column/webhook_test.go (5 hunks)
  • internal/connectors/plugins/public/currencycloud/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/currencycloud/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/currencycloud/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/currencycloud/payments_test.go (1 hunks)
  • internal/connectors/plugins/public/currencycloud/payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/currencycloud/transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/generic/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/generic/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/generic/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/generic/payments_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/bank_account_creation_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/payments_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/users_test.go (1 hunks)
  • internal/connectors/plugins/public/mangopay/webhooks_test.go (2 hunks)
  • internal/connectors/plugins/public/modulr/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/modulr/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/modulr/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/modulr/payments_test.go (2 hunks)
  • internal/connectors/plugins/public/modulr/payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/modulr/transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/moneycorp/accounts_test.go (2 hunks)
  • internal/connectors/plugins/public/moneycorp/balances_test.go (2 hunks)
  • internal/connectors/plugins/public/moneycorp/external_accounts_test.go (2 hunks)
  • internal/connectors/plugins/public/moneycorp/payments_test.go (5 hunks)
  • internal/connectors/plugins/public/moneycorp/payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/moneycorp/transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/stripe/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/stripe/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/stripe/create_payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/stripe/create_transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/stripe/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/stripe/payments_test.go (2 hunks)
  • internal/connectors/plugins/public/stripe/reverse_transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/balances_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/external_accounts_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/payments_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/payouts_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/plugin_test.go (3 hunks)
  • internal/connectors/plugins/public/wise/profiles_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/transfers_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/uninstall_test.go (1 hunks)
  • internal/connectors/plugins/public/wise/webhooks_test.go (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • internal/connectors/plugins/public/column/client/error.go
🚧 Files skipped from review as they are similar to previous changes (68)
  • internal/connectors/plugins/public/atlar/accounts_test.go
  • internal/connectors/plugins/public/mangopay/external_accounts_test.go
  • internal/connectors/plugins/public/moneycorp/transfers_test.go
  • internal/connectors/plugins/public/mangopay/webhooks_test.go
  • internal/connectors/plugins/public/mangopay/balances_test.go
  • internal/connectors/plugins/public/modulr/balances_test.go
  • internal/connectors/plugins/public/stripe/accounts_test.go
  • internal/connectors/plugins/public/stripe/balances_test.go
  • internal/connectors/plugins/public/generic/external_accounts_test.go
  • internal/connectors/plugins/public/mangopay/bank_account_creation_test.go
  • internal/connectors/plugins/public/generic/accounts_test.go
  • internal/connectors/plugins/pub 7438 lic/bankingcircle/transfers_test.go
  • internal/connectors/plugins/public/wise/payouts_test.go
  • internal/connectors/plugins/public/mangopay/payments_test.go
  • internal/connectors/plugins/public/atlar/payouts_test.go
  • internal/connectors/plugins/public/currencycloud/external_accounts_test.go
  • internal/connectors/plugins/public/bankingcircle/payouts_test.go
  • internal/connectors/plugins/public/generic/payments_test.go
  • internal/connectors/plugins/public/atlar/external_accounts_test.go
  • internal/connectors/plugins/public/currencycloud/accounts_test.go
  • internal/connectors/plugins/public/wise/profiles_test.go
  • internal/connectors/plugins/public/column/webhook.go
  • internal/connectors/plugins/public/wise/payments_test.go
  • internal/connectors/plugins/public/wise/uninstall_test.go
  • internal/connectors/plugins/public/moneycorp/payments_test.go
  • internal/connectors/plugins/public/moneycorp/accounts_test.go
  • internal/connectors/plugins/public/modulr/external_accounts_test.go
  • internal/connectors/plugins/public/atlar/payments_test.go
  • internal/connectors/plugins/public/wise/transfers_test.go
  • internal/connectors/plugins/public/generic/balances_test.go
  • internal/connectors/plugins/public/mangopay/payouts_test.go
  • internal/connectors/plugins/public/atlar/bank_account_creation_test.go
  • internal/connectors/plugins/public/adyen/accounts_test.go
  • internal/connectors/plugins/public/stripe/reverse_transfers_test.go
  • internal/connectors/plugins/public/bankingcircle/bank_account_creation_test.go
  • internal/connectors/plugins/public/stripe/create_payouts_test.go
  • internal/connectors/plugins/public/mangopay/transfers_test.go
  • internal/connectors/plugins/public/column/payments_test.go
  • internal/connectors/plugins/public/bankingcircle/accounts_test.go
  • internal/connectors/plugins/public/modulr/payouts_test.go
  • internal/connectors/plugins/public/column/accounts_test.go
  • internal/connectors/plugins/public/wise/external_accounts_test.go
  • internal/connectors/plugins/public/moneycorp/external_accounts_test.go
  • internal/connectors/plugins/public/wise/webhooks_test.go
  • internal/connectors/plugins/public/moneycorp/payouts_test.go
  • internal/connectors/plugins/public/wise/balances_test.go
  • internal/connectors/plugins/public/column/balances_test.go
  • internal/connectors/plugins/public/modulr/payments_test.go
  • internal/connectors/plugins/public/column/reverse_payout_test.go
  • internal/connectors/plugins/public/moneycorp/balances_test.go
  • internal/connectors/plugins/public/column/external_accounts_test.go
  • internal/connectors/plugins/public/currencycloud/payments_test.go
  • internal/connectors/plugins/public/mangopay/accounts_test.go
  • internal/connectors/plugins/public/currencycloud/payouts_test.go
  • internal/connectors/plugins/public/bankingcircle/balances_test.go
  • internal/connectors/plugins/public/atlar/balances_test.go
  • internal/connectors/plugins/public/modulr/accounts_test.go
  • internal/connectors/plugins/public/column/payouts_test.go
  • internal/connectors/plugins/public/stripe/create_transfers_test.go
  • internal/connectors/plugins/public/bankingcircle/payments_test.go
  • internal/connectors/plugins/public/currencycloud/transfers_test.go
  • internal/connectors/plugins/public/currencycloud/balances_test.go
  • internal/connectors/plugins/public/column/transfers_test.go
  • internal/connectors/plugins/public/wise/accounts_test.go
  • internal/connectors/plugins/public/stripe/external_accounts_test.go
  • internal/connectors/plugins/public/modulr/transfers_test.go
  • internal/connectors/plugins/public/adyen/webhooks.go
  • internal/connectors/plugins/public/mangopay/users_test.go
🧰 Additional context used
🧬 Code Graph Analysis (2)
internal/connectors/plugins/public/stripe/payments_test.go (1)
internal/connectors/plugins/public/stripe/client/client_generated.go (1)
  • MockClient (21-25)
internal/connectors/plugins/public/adyen/plugin_test.go (1)
internal/connectors/plugins/public/adyen/client/client_generated.go (1)
  • NewMockClient (35-39)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Tests
🔇 Additional comments (35)
internal/connectors/plugins/public/adyen/webhooks_test.go (9)

5-6: Good use of crypto libraries for idempotency key generation.

Adding the crypto/sha256 and encoding/base64 packages supports the new approach of generating a persistent idempotency key from the webhook payload.


22-25: Excellent improvement using the interface instead of concrete type.

Changing from plg *Plugin to plg models.Plugin enforces the interface contract and will catch type mismatches in function signatures. This addresses one of the PR objectives about fixing plugin tests to use the interface rather than concrete struct.


28-34: Good practice with controller initialization and interface assignment.

The initialization logic follows best practices by:

  1. Creating a controller with proper test lifecycle management
  2. Initializing the plugin with the mock client
  3. Assigning the concrete plugin to the interface reference

This ensures plugin methods are called through the interface rather than directly on the concrete type.


37-39: Important fix: Added proper mock controller teardown.

Adding the AfterEach hook with ctrl.Finish() ensures that mock expectations are verified at the end of each test, avoiding potential memory leaks and unchecked mock expectations.


76-103: Well-structured test setup for the new webhook verification flow.

The test context properly initializes a standard webhook payload that will be used across multiple test cases, making the tests more maintainable and readable.


104-127: Good test coverage for basic auth failure scenario.

This test ensures that basic authentication is properly verified before processing webhooks, providing a critical security check.


129-150: Comprehensive test for HMAC verification failure.

The test properly:

  1. Marshals the webhook to JSON
  2. Sets up the request object
  3. Verifies basic auth (which succeeds)
  4. Translates the webhook payload
  5. Tests that HMAC verification fails
  6. Ensures no idempotency key is returned on verification failure

This tests the complete verification flow and failure handling.


152-175: Well-implemented success case with idempotency key generation.

This test properly verifies the entire successful flow and confirms the idempotency key is correctly generated as a base64-encoded SHA-256 hash of the webhook body. This is a critical component of the PR's objective to move idempotency checks to persistent storage.


468-476: Good refactoring of the helper function signature.

Updating doTranslateCall to accept the interface type (models.Plugin) instead of the concrete type promotes better testing practices and alignment with the PR objectives.

internal/connectors/plugins/public/adyen/plugin_test.go (3)

24-25: Good practice: Added mock controller at the suite level.

Adding the controller as a suite-level variable enables proper management of mocks across all tests within the suite.


33-34: Properly initialized mock controller with GinkgoT().

Using GinkgoT() ensures that test failures are properly reported through the Ginkgo testing framework.


37-39: Important fix: Added mock controller teardown.

Adding the AfterEach hook with ctrl.Finish() ensures that mock expectations are verified after each test, avoiding potential memory leaks and ensuring all expected mock calls are made.

internal/connectors/plugins/public/column/uninstall_test.go (5)

18-19: Good improvement: Using the interface type for tests

Changing from a concrete Plugin type to the interface type models.Plugin helps ensure type-safety and enforces that the tests use only the public API.


27-28: Properly initialized mock controller

Good practice to initialize the controller at the suite level using gomock.NewController().


34-38: Improved client initialization

Better structure by creating the client first, then setting the mock HTTP client, and finally assigning to the plugin.


41-43: Added essential mock controller cleanup

Adding ctrl.Finish() in AfterEach ensures proper cleanup of mock expectations, preventing leaks between tests.


62-62: Using exported method instead of unexported

Switching from uninstall to Uninstall ensures tests use the public API, maintaining compatibility with the interface contract.

Also applies to: 79-79, 137-137, 199-199

internal/connectors/plugins/public/column/bank_account_creation_test.go (4)

16-19: Using the interface type and better variable organization

Good change to use models.Plugin instead of concrete type and organize mock-related variables at the suite level.


21-27: Centralized mock initialization

Properly initializing the mock controller, HTTP client, and plugin in BeforeEach simplifies test setup.


29-31: Added proper mock cleanup

Adding ctrl.Finish() in AfterEach ensures expectations are verified and resources cleaned up after each test.


33-43: Using public API for testing

Tests now use the public CreateBankAccount method instead of internal validation functions, better reflecting real usage patterns.

internal/connectors/plugins/public/column/webhook_test.go (7)

19-22: Better test structure with interface type and mocks

Converting to models.Plugin interface and properly organizing mock variables improves the test structure.


26-38: Comprehensive test setup with verifier mock

Good initialization of all required components, including the new webhook verifier mock, which is essential for testing the webhook verification functionality.


40-42: Proper mock controller cleanup

Adding ctrl.Finish() in AfterEach ensures expectations are verified and resources cleaned up after each test.


79-96: Dynamic webhook testing based on plugin configuration

Instead of hardcoding webhook test values, the test now loops through all supported webhooks from the plugin, making the test more maintainable and comprehensive.


125-138: Updated to test new webhook verification flow

Test now correctly uses the new VerifyWebhook method with the appropriate request structure and validates the expected error message for missing signature header.


163-185: Good test for successful verification with idempotency key

This test properly validates that the successful verification returns an idempotency key, which is crucial for the webhook deduplication mechanism.


187-201: Specific error testing for unknown webhook types

Test now correctly expects the specific error for unknown webhook types from the plugin.

internal/connectors/plugins/public/wise/plugin_test.go (4)

104-173: Good separation of verification and translation concerns

The new context for "verifying webhook" properly isolates webhook verification tests from translation tests, which aligns with the architectural change to separate these concerns in the plugin interface. The setup with a gomock controller at the context level and proper cleanup in AfterEach improves mock lifecycle management.


135-137: Proper mock lifecycle management

Good practice to call ctrl.Finish() in AfterEach to ensure that all expected mock calls were made and clean up resources. This fixes a common source of test flakiness where mocks aren't properly verified between tests.


175-274: Well-structured test context separation

The restructured "translate webhook" context now focuses solely on webhook translation without verification concerns. This is a good separation of concerns and aligns with the split into VerifyWebhook and TranslateWebhook in the plugin interface.


206-208: Consistent mock lifecycle management

Good to see consistent mock lifecycle management across both contexts, with the controller created in BeforeEach and ctrl.Finish() called in AfterEach. This consistency improves test reliability and readability.

internal/connectors/plugins/public/stripe/payments_test.go (3)

28-29: Improved mock controller lifecycle management

By moving the ctrl variable declaration to the file-level context, it's now accessible from both BeforeEach and AfterEach blocks, enabling proper cleanup.


37-38: Proper instantiation of mock controller

The controller is now properly initialized with GinkgoT() and assigned to the test-context variable.


192-194: Added proper mock verification with AfterEach

This is a critical improvement. Adding ctrl.Finish() in an AfterEach block ensures:

  1. All expected mock calls are verified
  2. Resources are properly cleaned up after each test
  3. Any unfulfilled expectations are reported as test failures

This change aligns with gomock best practices and prevents potential false positives in tests.

@paul-nicolas paul-nicolas force-pushed the feat/webhooks-idempotency-key branch from f737ff7 to e1820d1 Compare May 14, 2025 15: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: 0

♻️ Duplicate comments (2)
internal/connectors/plugins/public/column/webhook_test.go (2)

40-42: Fixed previous issue with controller finalization

Adding ctrl.Finish() in AfterEach properly addresses a previous review comment about preventing lingering mock expectations. This ensures proper cleanup after each test.


124-138: Updated verification error handling with new header name

The test properly uses the current header name "Column-Signature" in the error message, addressing a previous review comment about outdated header names.

🧹 Nitpick comments (2)
internal/connectors/plugins/public/column/webhook_test.go (2)

80-96: Improved webhook creation test with better maintainability

Iterating over the actual supported webhooks from the plugin is much more maintainable than hardcoding expectations. However, casting back to the concrete type breaks the abstraction provided by using the interface.

Consider adding a method to the interface to access the webhook configuration:

// in models/plugin.go (or appropriate interface file)
+GetSupportedWebhooks() map[string]WebhookConfig

Then update the test:

-for name, w := range plg.(*Plugin).supportedWebhooks {
+for name, w := range plg.GetSupportedWebhooks() {

100-100: Type safety in assertion

This line references the concrete type to get the supportedWebhooks length, which is consistent with line 80 but breaks the interface abstraction.

If you implement the previously suggested GetSupportedWebhooks() method, this could be updated to:

-Expect(res.Others).To(HaveLen(len(plg.(*Plugin).supportedWebhooks)))
+Expect(res.Others).To(HaveLen(len(plg.GetSupportedWebhooks())))
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f737ff7 and e1820d1.

📒 Files selected for processing (7)
  • internal/connectors/engine/activities/errors.go (1 hunks)
  • internal/connectors/plugins/public/adyen/webhooks.go (3 hunks)
  • internal/connectors/plugins/public/adyen/webhooks_test.go (7 hunks)
  • internal/connectors/plugins/public/column/webhook.go (1 hunks)
  • internal/connectors/plugins/public/column/webhook_test.go (5 hunks)
  • internal/connectors/plugins/public/wise/plugin.go (2 hunks)
  • internal/models/errors.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/connectors/plugins/public/column/webhook.go
  • internal/models/errors.go
  • internal/connectors/engine/activities/errors.go
  • internal/connectors/plugins/public/adyen/webhooks.go
  • internal/connectors/plugins/public/wise/plugin.go
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Tests
🔇 Additional comments (12)
internal/connectors/plugins/public/adyen/webhooks_test.go (8)

5-6: Added required imports for crypto operations.

These imports support the new webhook verification functionality, enabling SHA256 hash calculation and base64 encoding for idempotency key generation.


22-24: Switched to interface-based testing.

Good change - using the models.Plugin interface instead of the concrete Plugin struct ensures proper type checking of all plugin interface methods, aligning with the PR objective to detect type errors in function overrides.


28-34: Improved test setup with gomock controller.

The test now properly initializes the mock controller and configures the plugin, maintaining the concrete implementation while exposing it through the interface. This approach ensures type safety while testing against the interface contract.


37-39: Added proper mock verification.

Adding AfterEach with ctrl.Finish() ensures all mock expectations are verified after each test, preventing potential mock verification leaks between tests.


47-47: Updated method calls to use the plugin interface.

All method calls now properly use the interface instead of concrete implementation, ensuring type safety and consistency with the interface-based testing approach.

Also applies to: 57-57, 70-72


76-176: Added comprehensive verification tests for the new webhook verification flow.

This test context thoroughly tests the new VerifyWebhook method, covering:

  1. Authentication failures (basic auth)
  2. Signature verification failures (HMAC)
  3. Successful verification with proper idempotency key generation

The idempotency key implementation (SHA256 hash of the webhook body encoded in base64) provides a robust way to uniquely identify webhooks, addressing the issue mentioned in the PR objectives where replayed webhooks could be processed multiple times if the original Temporal workflow was deleted.


470-470: Updated helper function to use the plugin interface.

The doTranslateCall helper now accepts the models.Plugin interface instead of a concrete type, making it consistent with the interface-based testing approach.


511-514: Simplified webhook translation test.

The test now focuses only on translation functionality since verification has been separated into its own method and test context. This separation of concerns improves test clarity and maintainability.

internal/connectors/plugins/public/column/webhook_test.go (4)

19-22: Interface type improvement for better type safety

The change from a concrete Plugin type to the interface models.Plugin aligns with the PR goal to use interface types in tests. This ensures proper type checking of implemented plugin interface functions.


26-38: Well-structured test setup with proper mocking

The refactored test setup nicely centralizes the initialization of the controller, HTTP client, and webhook verifier, making the tests more maintainable and consistent.


163-185: Well-implemented webhook verification success test

This test properly verifies the new idempotency key functionality. The test confirms that webhook verification returns an idempotency key (the webhook event ID "1") when successful.


183-184:

✅ Verification successful

Verify idempotency key extraction logic

The test expects the idempotency key to be "1", which appears to be extracted from the webhook payload's id field. Ensure this is the correct field to use as the idempotency key according to your design.


🏁 Script executed:

#!/bin/bash
# Check where the idempotency key value is extracted from in the implementation
rg -A 5 "WebhookIdempotencyKey" --type go

Length of output: 7098


Idempotency key extraction logic for Column plugin verified

The Column connector’s translateWebhook implementation sets the idempotency key to the payload’s ID field (&webhook.ID), which aligns with the test’s use of pointer.For("1"). No changes are necessary.

  • internal/connectors/plugins/public/column/webhook.go:
    WebhookIdempotencyKey: &webhook.ID

@paul-nicolas paul-nicolas added this pull request to the merge queue May 14, 2025
Merged via the queue into main with commit 9dbfa28 May 14, 2025
8 of 9 checks passed
@paul-nicolas paul-nicolas deleted the feat/webhooks-idempotency-key branch May 14, 2025 15:31
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
0