# QA Outline — Salesforce Connector for iPaaS

**Version**: 1.0  
**Date**: 2026-03-30  
**Feature**: Salesforce REST API connector integration  
**Tester prerequisite**: Access to a Salesforce sandbox org with a Connected App configured (scopes: `api`, `refresh_token`, `offline_access`), and access to the SuiteX iPaaS UI as an admin user.

---

## Prerequisites & Environment Setup

Before beginning any test section, confirm the following are true:

- [ ] You are logged into SuiteX as a tenant admin with iPaaS permissions
- [ ] You have credentials for both a **Salesforce Production** org and a **Salesforce Sandbox** org
- [ ] Each org has a Connected App created with:
  - OAuth callback URL set to `https://app.suitex.com/tenant/ipaas/connector/oauth/callback`
  - Scopes: `api`, `refresh_token` (Salesforce labels this "Perform requests at any time")
- [ ] The `connector_config` database table has an `instance_url` column (confirm with a developer if unsure)
- [ ] The application type dropdown in the iPaaS connector form shows both **Salesforce Production (OAuth 2.0)** and **Salesforce Sandbox (OAuth 2.0)** as options

---

## Section 1 — Connector Creation & OAuth Authentication

These tests verify that a Salesforce connector can be created and authenticated end-to-end.

---

### TC-SF-001 — Create a Salesforce Production connector

**Goal**: Confirm the full OAuth authorization flow works for a production org.

**Steps**:
1. Navigate to iPaaS → Connectors → New Connector
2. Select application type: **Salesforce Production (OAuth 2.0)**
3. Enter a connector name, e.g. `SF Production Test`
4. Fill in Client ID and Client Secret from your Salesforce Connected App
5. Set grant type to **Authorization Code**
6. Click Save / Authorize

**Expected**:
- Browser redirects to `login.salesforce.com`
- After approving in Salesforce, you are returned to SuiteX
- Connector status shows **Active**
- No error messages are displayed

**Pass/Fail**: ___

---

### TC-SF-002 — Create a Salesforce Sandbox connector

**Goal**: Confirm the sandbox OAuth flow routes to the correct Salesforce login page.

**Steps**:
1. Repeat TC-SF-001 using application type: **Salesforce Sandbox (OAuth 2.0)**
2. After clicking Authorize, confirm the browser redirects to `test.salesforce.com` (not `login.salesforce.com`)
3. Approve in Salesforce

**Expected**:
- Browser goes to `test.salesforce.com` (not the production login page)
- Connector status shows **Active** after returning to SuiteX

**Pass/Fail**: ___

---

### TC-SF-003 — Instance URL is stored after OAuth callback

**Goal**: Confirm the dynamic Salesforce instance URL (e.g. `https://na50.salesforce.com`) is captured and saved.

**Steps**:
1. Create and authorize a Salesforce connector (either TC-SF-001 or TC-SF-002)
2. Ask a developer to run the following query against the `connector_config` table:
   ```sql
   SELECT id, name, instance_url FROM connector_config WHERE name = 'SF Production Test';
   ```

**Expected**:
- `instance_url` column is populated with a URL in the format `https://<pod>.salesforce.com` or `https://<subdomain>.sandbox.salesforce.com`
- It is **not** blank or null

**Pass/Fail**: ___

---

### TC-SF-004 — Tokens are encrypted in the database

**Goal**: Confirm sensitive tokens are not stored in plain text.

**Steps**:
1. After creating and authorizing a connector, ask a developer to inspect the `access_token` and `refresh_token` columns in `connector_config`

**Expected**:
- Values in `access_token` and `refresh_token` look like encrypted ciphertext, not a readable JWT or Salesforce token string
- `instance_url` is stored as plain text (it is not sensitive)

**Pass/Fail**: ___

---

### TC-SF-005 — Invalid Client ID shows an error

**Goal**: Confirm bad credentials produce a meaningful error, not a blank screen.

**Steps**:
1. Create a new Salesforce connector with a deliberately wrong Client ID (e.g. `INVALID_CLIENT_123`)
2. Click Authorize

**Expected**:
- An error message is displayed, containing text similar to "invalid_client_id" or "Authentication failed"
- The connector is **not** saved as Active

**Pass/Fail**: ___

---

### TC-SF-006 — User denying authorization shows an error

**Goal**: Confirm that clicking "Deny" on the Salesforce authorization page is handled gracefully.

**Steps**:
1. Create a new Salesforce connector with valid credentials
2. Click Authorize — when the Salesforce authorization page loads, click **Deny**

**Expected**:
- Returned to SuiteX with an error message, e.g. "Authorization denied by user"
- Connector is not saved as Active

**Pass/Fail**: ___

---

## Section 2 — SOQL Query Execution

These tests verify that Salesforce Object Query Language (SOQL) queries execute correctly through an iPaaS flow API node.

---

### TC-SF-007 — Basic SOQL query returns records

**Goal**: Confirm a simple query returns the expected records in the flow preview.

**Pre-condition**: An active Salesforce connector exists (from Section 1). Your Salesforce org has Contact records.

**Steps**:
1. Open an iPaaS flow with a Salesforce API node
2. Configure the node:
   - Method: `GET`
   - URL: `/services/data/v59.0/query/?q=SELECT Id, Name FROM Contact LIMIT 50`
3. Run the flow preview

**Expected**:
- Response contains `totalSize`, `done`, and `records` keys
- `records` array contains up to 50 Contact objects, each with `Id` and `Name`
- No error is shown

**Pass/Fail**: ___

---

### TC-SF-008 — SOQL syntax error returns a descriptive error

**Goal**: Confirm that a bad SOQL query fails with a readable error, not a generic 500.

**Steps**:
1. Configure a Salesforce API node with an invalid query:
   - URL: `/services/data/v59.0/query/?q=SELECT InvalidField FROM Contact`
2. Run the preview

**Expected**:
- Response contains an error message referencing `INVALID_FIELD` or similar Salesforce error code
- Flow does not complete successfully

**Pass/Fail**: ___

---

### TC-SF-009 — Large result set triggers automatic pagination

**Goal**: Confirm that a query returning more than 2,000 records automatically fetches all pages.

**Pre-condition**: Your Salesforce org has more than 2,000 records of the object being queried (e.g. Contact).

**Steps**:
1. Configure a Salesforce API node with a query that will return 5,000+ records:
   - URL: `/services/data/v59.0/query/?q=SELECT Id, Name FROM Contact`
2. Run the flow (not just preview — full execution)
3. After completion, check the flow execution logs

**Expected**:
- Flow logs show multiple page fetches (the `nextRecordsUrl` is followed for each page)
- Final result set contains all records across all pages
- Flow status is **Completed Successfully**
- No records are missing

**Pass/Fail**: ___

---

## Section 3 — Record Create (POST)

---

### TC-SF-010 — Create a Salesforce Contact record via POST

**Goal**: Confirm a new record can be created through iPaaS.

**Steps**:
1. Configure a Salesforce API node:
   - Method: `POST`
   - URL: `/services/data/v59.0/sobjects/Contact/`
   - Body: `{"FirstName": "QA", "LastName": "TestUser", "Email": "qa-test@example.com"}`
2. Run the flow

**Expected**:
- HTTP 201 response
- Response body contains `id` (the new Salesforce record ID) and `"success": true`
- Log in to Salesforce and confirm the Contact "QA TestUser" was created

**Pass/Fail**: ___

---

### TC-SF-011 — Record ID is available to downstream nodes

**Goal**: Confirm the new record's Salesforce ID is accessible in subsequent flow nodes.

**Steps**:
1. Add a downstream node (e.g. a Map or Log node) after the POST node from TC-SF-010
2. Reference `{{payload.id}}` in the downstream node
3. Run the flow

**Expected**:
- The downstream node receives the Salesforce ID (e.g. `003xx000004TmiYABC`)
- It is the same ID that appears in Salesforce for the created record

**Pass/Fail**: ___

---

### TC-SF-012 — Missing required field returns a validation error

**Goal**: Confirm Salesforce validation errors are surfaced, not swallowed.

**Steps**:
1. POST to `/services/data/v59.0/sobjects/Contact/` with only `{"FirstName": "QA"}` (no `LastName`)

**Expected**:
- Error response containing `REQUIRED_FIELD_MISSING` and a message referencing `LastName`
- Flow node marked as failed

**Pass/Fail**: ___

---

### TC-SF-013 — Invalid field name returns a validation error

**Goal**: Confirm a typo in a field name is caught.

**Steps**:
1. POST to `/services/data/v59.0/sobjects/Contact/` with `{"LastName": "Test", "Emaill": "bad@field.com"}` (double `l`)

**Expected**:
- Error response containing `INVALID_FIELD` and a reference to `Emaill`
- Record is **not** created in Salesforce

**Pass/Fail**: ___

---

## Section 4 — Record Read (GET)

---

### TC-SF-014 — Read an existing record by ID

**Goal**: Confirm a specific record can be retrieved by its Salesforce ID.

**Steps**:
1. Use a valid Salesforce Contact ID from your org (e.g. obtained from TC-SF-010)
2. Configure an API node:
   - Method: `GET`
   - URL: `/services/data/v59.0/sobjects/Contact/{id}` (replace `{id}` with the real ID)
3. Run the flow

**Expected**:
- HTTP 200 response
- Response body contains all fields for that Contact
- Response includes an `attributes` key with `type` and `url`

**Pass/Fail**: ___

---

### TC-SF-015 — Non-existent record ID returns 404

**Goal**: Confirm that requesting a record that does not exist returns a 404, not a 500.

**Steps**:
1. Configure a GET node with a fake ID: `/services/data/v59.0/sobjects/Contact/000DOESNOTEXIST`

**Expected**:
- HTTP 404 response or an error message indicating the record was not found
- Flow handles this gracefully (does not crash)

**Pass/Fail**: ___

---

## Section 5 — Record Update & Upsert (PATCH)

---

### TC-SF-016 — Update an existing record via PATCH

**Goal**: Confirm a specific field on an existing record can be updated.

**Pre-condition**: Use the Contact created in TC-SF-010.

**Steps**:
1. Configure an API node:
   - Method: `PATCH`
   - URL: `/services/data/v59.0/sobjects/Contact/{id}` (use the Contact ID from TC-SF-010)
   - Body: `{"Email": "updated-qa@example.com"}`
2. Run the flow

**Expected**:
- HTTP 204 No Content (no body in the response)
- Flow node completes successfully
- Log in to Salesforce and confirm the Email field is now `updated-qa@example.com`
- Other fields (FirstName, LastName) are **unchanged**

**Pass/Fail**: ___

---

### TC-SF-017 — Upsert creates a new record when external ID does not exist

**Goal**: Confirm upsert creates a record when the external ID is new.

**Pre-condition**: Your Salesforce Contact object has an external ID field, e.g. `External_ID__c`.

**Steps**:
1. Configure an API node:
   - Method: `PATCH`
   - URL: `/services/data/v59.0/sobjects/Contact/External_ID__c/QA-UPSERT-001`
   - Body: `{"LastName": "UpsertTest", "Email": "upsert@example.com"}`
2. Run the flow

**Expected**:
- HTTP 201 (record was created)
- New Contact visible in Salesforce with `External_ID__c = QA-UPSERT-001`

**Pass/Fail**: ___

---

### TC-SF-018 — Upsert updates an existing record when external ID matches

**Goal**: Confirm upsert does not create a duplicate when the external ID already exists.

**Pre-condition**: TC-SF-017 has been run (record with `External_ID__c = QA-UPSERT-001` exists).

**Steps**:
1. Run the same PATCH request from TC-SF-017, but change the email:
   - Body: `{"LastName": "UpsertTest", "Email": "upsert-v2@example.com"}`

**Expected**:
- HTTP 200 (record was updated, not created)
- Only **one** Contact with `External_ID__c = QA-UPSERT-001` exists in Salesforce (no duplicate)
- Email is now `upsert-v2@example.com`

**Pass/Fail**: ___

---

## Section 6 — Token Refresh

---

### TC-SF-019 — Expired token triggers automatic refresh

**Goal**: Confirm that an expired access token is refreshed automatically without user intervention.

**Pre-condition**: Ask a developer to manually set the `expires` value in `connector_config` for your Salesforce connector to a past timestamp (e.g. `1` or a Unix timestamp from yesterday).

**Steps**:
1. With the token artificially expired, run a simple flow (e.g. TC-SF-007)

**Expected**:
- Flow completes successfully
- Flow logs show a token refresh sequence: "Token refresh initiated" → "Token refresh successful" → API call proceeds
- No error is shown to the user
- The `access_token` value in the database has changed to a new value

**Pass/Fail**: ___

---

### TC-SF-020 — New access token is persisted after refresh

**Goal**: Confirm the refreshed token is saved so subsequent requests reuse it.

**Steps**:
1. After TC-SF-019 passes, ask a developer to check `connector_config.access_token` and `connector_config.expires`

**Expected**:
- `expires` has been updated to a future timestamp
- `access_token` contains a new (different) encrypted value compared to before the test

**Pass/Fail**: ___

---

## Section 7 — Error Handling

---

### TC-SF-021 — QUERY_TIMEOUT logs a warning and fails the flow

**Goal**: Confirm a timed-out Salesforce query fails gracefully with a log entry.

**Note**: This scenario is difficult to trigger manually. Confirm with a developer that this error code path has been tested in code review. Skip to TC-SF-022 if a `QUERY_TIMEOUT` response cannot be simulated in your environment.

**Expected behaviour** (for developer confirmation):
- Flow node is marked failed
- Application logs contain a warning entry referencing `QUERY_TIMEOUT` and the connector/flow IDs
- No retry is attempted

**Pass/Fail / Developer Confirmed**: ___

---

### TC-SF-022 — All errors include connector ID, flow ID, and error code in logs

**Goal**: Confirm that error log entries contain the minimum required context for debugging.

**Steps**:
1. Trigger any error condition (e.g. TC-SF-008 — the bad SOQL query)
2. Ask a developer to check the application logs (e.g. `storage/logs/laravel.log` or the log management tool)

**Expected**:
- Log entry includes:
  - `connector_id` or connector name
  - `flow_id` or flow run identifier
  - `errorCode` (e.g. `INVALID_FIELD`, `MALFORMED_QUERY`)
  - The error message text

**Pass/Fail**: ___

---

## Section 8 — Multi-Environment Isolation

---

### TC-SF-023 — Production and Sandbox connectors use separate instance URLs

**Goal**: Confirm each connector calls its own Salesforce environment and they do not cross-contaminate.

**Pre-condition**: Both a Production connector (TC-SF-001) and a Sandbox connector (TC-SF-002) have been created.

**Steps**:
1. Run a simple query on the Production connector and note the data returned
2. Run the same query on the Sandbox connector and note the data returned
3. Ask a developer to verify `connector_config.instance_url` for each record

**Expected**:
- Production connector's `instance_url` resembles `https://na<XX>.salesforce.com`
- Sandbox connector's `instance_url` resembles `https://<name>.sandbox.salesforce.com`
- Data returned by each is different (the two orgs are separate environments)
- Neither connector's token can be used to call the other's instance URL

**Pass/Fail**: ___

---

### TC-SF-024 — Each connector uses its own access token

**Goal**: Confirm tokens are not shared between connectors.

**Steps**:
1. Ask a developer to query `connector_config` for both connectors and compare `access_token` values

**Expected**:
- The two `access_token` values are different
- The two `instance_url` values are different

**Pass/Fail**: ___

---

## Section 9 — End-to-End Integration

---

### TC-SF-025 — Salesforce Query → Transform → NetSuite Create flow

**Goal**: Confirm a cross-system flow runs successfully from start to finish.

**Pre-condition**: An active Salesforce connector and an active NetSuite connector both exist. The NetSuite connector can create Contact/Customer records.

**Steps**:
1. Build or open a flow with three nodes:
   - **Node 1 (Salesforce GET)**: `SELECT Id, Name, Email FROM Contact LIMIT 100`
   - **Node 2 (Transform/Map)**: Map Salesforce `Name` → NetSuite `entityId`, `Email` → `email`
   - **Node 3 (NetSuite POST)**: Create 100 Customer records in NetSuite
2. Run the full flow (not preview)
3. Wait for the flow to complete

**Expected**:
- Flow status shows **Completed Successfully**
- Flow logs show 100 records processed
- Log in to NetSuite and confirm 100 new Customer records exist with the correct names and emails
- Flow duration is approximately 30–60 seconds (not instant, not hours)

**Pass/Fail**: ___

---

## Section 10 — Regression Check

These checks confirm the Salesforce implementation has not broken existing iPaaS functionality.

---

### TC-SF-026 — Existing non-Salesforce connectors still work

**Goal**: Confirm that changes made for Salesforce have not broken other connector types.

**Steps**:
1. Open an existing flow that uses a non-Salesforce connector (e.g. NetSuite, HTTP, or a Basic Auth connector)
2. Run the flow

**Expected**:
- Flow executes as it did before this release
- No unexpected errors or behaviour changes

**Pass/Fail**: ___

---

### TC-SF-027 — HTTP 204 No Content responses are handled correctly for all connectors

**Goal**: Confirm the HTTP 204 fix (used by Salesforce PATCH) does not break other connectors that also return 204.

**Steps**:
1. If any existing connector in your environment can return HTTP 204 (e.g. a REST API DELETE endpoint), run a flow that triggers that response

**Expected**:
- The flow node completes successfully
- The flow does not error on a 204 response
- Downstream nodes receive `{"success": true, "httpStatusCode": 204}` (confirm with a developer if needed)

**Pass/Fail**: ___

---

## Summary Checklist

| Section | Tests | Pass | Fail | Blocked |
|---|---|---|---|---|
| 1 — OAuth Authentication | TC-SF-001 to 006 | | | |
| 2 — SOQL Query Execution | TC-SF-007 to 009 | | | |
| 3 — Record Create (POST) | TC-SF-010 to 013 | | | |
| 4 — Record Read (GET) | TC-SF-014 to 015 | | | |
| 5 — Record Update/Upsert (PATCH) | TC-SF-016 to 018 | | | |
| 6 — Token Refresh | TC-SF-019 to 020 | | | |
| 7 — Error Handling | TC-SF-021 to 022 | | | |
| 8 — Multi-Environment Isolation | TC-SF-023 to 024 | | | |
| 9 — End-to-End Integration | TC-SF-025 | | | |
| 10 — Regression | TC-SF-026 to 027 | | | |
| **Total** | **27** | | | |

---

## Known Out-of-Scope Items (Not Tested in This Cycle)

The following were explicitly excluded from this release. Do not raise defects against them.

- Composite API (batch multiple operations in a single request)
- Bulk API for large data loads (>10,000 records)
- Streaming API / real-time events
- SOQL query builder UI in the flow editor
- Automatic API version upgrades (fixed at v59.0)
- `REQUEST_LIMIT_EXCEEDED` exponential backoff retry (infrastructure change required; currently maps to an error code only)
- `QUERY_TIMEOUT` automated retry (by design: logs warning and fails immediately)
