Test your integration
A SocialScope integration is mostly about refusing the right things: another user's return, another app's connection, a missing metric presented as zero, or success inferred from a redirect. This page explains where you can test, which cases to cover before launch, how to stub SocialScope in your own test suite, and what a passing test does and does not prove.
Where to test
| Where | What it proves |
|---|---|
| Your own test suite, SocialScope stubbed | Your routes, ownership checks, result handling and display rules |
| The dev deployment | The real handoff, origins, return URLs and a live provider consent |
| SocialScope's local fixture | The whole Connect journey with synthetic accounts, no live provider |
The dev deployment
- GraphQL:
https://connect.socialscope.hardscope.incdev.dev/graphql - Connect host:
https://connect.socialscope.hardscope.incdev.dev
Your test environment needs its own client registration and key. Origins on the dev deployment must be exact HTTPS origins on a public DNS name, with no port. http://localhost:3000 is not accepted, so test the browser flow from a deployed HTTPS environment of your app, registered as its own origin and return URL.
Disconnects behave differently on dev. On dev a disconnect affects only the one connection. In production, a YouTube or TikTok disconnect also suspends other accounts on that provider, cancels Connect attempts in progress and briefly makes the provider unavailable to new attempts, until its revoke settles. Dev testing cannot show those effects, so cover them with stubbed tests. See Connect.
Provider apps on dev can run in sandbox or testing mode, where only accounts added as testers can connect. Ask the SocialScope admin which test accounts to use for each provider. Readiness reports a provider as available only after the admin has enabled it for your app.
SocialScope's local fixture
SocialScope can run locally with a fake provider that returns synthetic profiles, posts and metrics. It runs the real hosted handoff on consumer.localhost and socialscope.localhost in Chromium. It needs access to the SocialScope source, so ask the SocialScope team if you want to use it. The fixture proves wiring. It does not prove provider approval or a live consent.
Cases to cover before launch
Connect
- Two different users in the same tenant each connect an account. Neither can list or read the other's connection.
- A second app key, registered as a separate client, cannot read the first app's connections. The read returns
NOT_FOUND. - The return route rejects a missing, malformed, duplicate, foreign or already used
ss_session_idwithout showing success. - A return for user A opened while signed in as user B is rejected.
- Opening the return URL twice with the same hint does not show success twice.
- With a provider unavailable in readiness, its button shows as unavailable. A failed readiness call also disables it.
- Declining at the provider (
DENIED) and letting an attempt expire (EXPIRED) both lead to a retry, not success. -
COMPLETEDwith a connection that is notCONNECTEDdoes not show as connected. - Each
syncStatusrenders correctly:PENDINGandRUNNINGas loading,READYas loaded,PARTIALas possibly incomplete,STALEwith its age,FAILEDas an error with a retry. - A stored connection whose read fails with
AUTHORIZATION_SUSPENDED,RECONNECT_REQUIRED,CAPABILITY_UNAVAILABLEorFORBIDDENshows that per-account state, and the other accounts still render. - Any
creatorActionshows the "remove the app at the provider" instruction. -
ss_session_idis gone from the URL before analytics or third-party scripts load.
Data
- A metric with
availabilityother thanAVAILABLErenders as "not available", never0. -
observedAtis shown next to counts. - A window with
completeForWindow: falseis not presented as the full list. -
SYNC_WINDOW_BUSYandRATE_LIMITEDlead to a later retry, not an error page loop.
Disconnect
- Disconnecting removes the account from your UI immediately.
- An Instagram disconnect always shows the "remove the app in Instagram settings" instruction.
- Your UI never claims a provider revoke unless the receipt is
SUCCEEDEDwith a nullcreatorAction.
Google identity, if used
- A new Google user creates one local user keyed by
issuerandgoogleSub. - A returning Google user signs in to the same local user, even if their email changed.
- A second redeem of the same result is rejected and signs nobody in.
-
DENIED,FAILEDandEXPIREDcreate no user. - A hint from another browser session is rejected.
Secrets
- The key is absent from every browser bundle. Search the built client assets for the key ID.
- Logs contain no
Authorizationheader, launch token or result proof.
Stub SocialScope in your own tests
Test your routes through their public interface and stub SocialScope at the HTTP boundary, the fetch to SOCIALSCOPE_GRAPHQL_URL. Answer with responses shaped like the schema, including error responses with extensions.code.
This Vitest example checks that a return route refuses a hint that does not belong to the signed-in user, without calling SocialScope at all. handleConnectReturn and seedAttempt stand in for your own route and test fixture.
import { afterEach, describe, expect, it, vi } from "vitest";
import { handleConnectReturn } from "../src/social/return-route";
import { seedAttempt } from "./fixtures";
const GRAPHQL_URL = "https://connect.socialscope.hardscope.incdev.dev/graphql";
function stubSocialScope(answer: (operation: string) => unknown) {
const calls: string[] = [];
vi.stubGlobal(
"fetch",
vi.fn(async (url: string, init: RequestInit) => {
expect(url).toBe(GRAPHQL_URL);
const { query } = JSON.parse(String(init.body)) as { query: string };
const operation = /(?:query|mutation)\s+(\w+)/.exec(query)?.[1] ?? "anonymous";
calls.push(operation);
return new Response(JSON.stringify(answer(operation)), { headers: { "content-type": "application/json" } });
}),
);
return calls;
}
afterEach(() => {
vi.unstubAllGlobals();
});
describe("connect return route", () => {
it("rejects another user's attempt without asking SocialScope", async () => {
// Arrange
process.env.SOCIALSCOPE_GRAPHQL_URL = GRAPHQL_URL;
process.env.SOCIALSCOPE_CONSUMER_KEY = "test-key-id.test-secret";
const attempt = await seedAttempt({ userId: "user-a", tenantKey: "example-workspace" });
const calls = stubSocialScope(() => ({ data: null }));
const url = new URL(`https://app.example.com/social/return?ss_session_id=${attempt.id}`);
// Act
const result = await handleConnectReturn(url, { userId: "user-b", tenantKey: "example-workspace" });
// Assert
expect(result.outcome).toBe("retry");
expect(calls).toEqual([]);
});
it("does not show success when the connection is no longer connected", async () => {
// Arrange
process.env.SOCIALSCOPE_GRAPHQL_URL = GRAPHQL_URL;
process.env.SOCIALSCOPE_CONSUMER_KEY = "test-key-id.test-secret";
const attempt = await seedAttempt({ userId: "user-a", tenantKey: "example-workspace" });
stubSocialScope((operation) =>
operation === "VerifyConnection"
? {
data: {
connectSessionResult: {
id: attempt.id,
provider: "INSTAGRAM",
status: "COMPLETED",
resultCode: "CONNECTED",
connectionId: "c0ffee00-0000-4000-8000-000000000001",
creatorAction: null,
},
},
}
: {
data: {
connection: {
id: "c0ffee00-0000-4000-8000-000000000001",
provider: "INSTAGRAM",
status: "RECONNECT_REQUIRED",
syncStatus: "STALE",
},
},
},
);
const url = new URL(`https://app.example.com/social/return?ss_session_id=${attempt.id}`);
// Act
const result = await handleConnectReturn(url, { userId: "user-a", tenantKey: "example-workspace" });
// Assert
expect(result.outcome).toBe("not-connected");
});
});
Use real keys only in the dev environment's secret store, never in test files.
Preview deployments
If your app deploys a preview per pull request on hosts that are only known at deploy time, SocialScope can serve one shared preview client per deployment. It matches a host template such as https://pr-*-example-preview.example.com, where * is the pull request number. Ask the SocialScope admin whether a preview client is configured for your app and which template and return paths it uses.
Rules for preview clients:
- The
*is a pull request number of 1 to 6 digits with no leading zero. A port,http, a different suffix or a shared hosting domain never matches. - Render the launch form on the concrete preview origin, the same origin as the return URL.
- Only Connect works. Google identity is never available to a preview client.
- Only provider apps in sandbox or testing mode serve preview clients, so only their test accounts can connect. Readiness can still report
truefor a provider that then refuses the attempt withFORBIDDEN. - All previews share one key and one tenant. Prefix
externalSubjectIdwith the pull request number, for examplepr-123:<userId>, so previews that clone the same users never share connections. SocialScope cannot enforce this for you.
What a passing test does not prove
- A stubbed suite proves your handling, not SocialScope's behavior or the provider's.
- The local fixture proves wiring, not provider approval.
integrationReadiness: trueproves configuration, not that a live consent will succeed.- Before launch, connect one real account per provider on the target environment, read its posts, and disconnect it.