From 42f170757871dbbe139a0b078dbdd00ffb40701f Mon Sep 17 00:00:00 2001 From: mishayouknowme Date: Tue, 9 Sep 2025 15:44:32 +0200 Subject: [PATCH 01/10] Add create team tests --- tests/api/helpers/auth0/index.ts | 1 + .../dev-portal-helpers/create-team.spec.ts | 127 ++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 tests/api/specs/dev-portal-helpers/create-team.spec.ts diff --git a/tests/api/helpers/auth0/index.ts b/tests/api/helpers/auth0/index.ts index 363d4c5da..70917ae98 100644 --- a/tests/api/helpers/auth0/index.ts +++ b/tests/api/helpers/auth0/index.ts @@ -1 +1,2 @@ export * from "./create-app-session"; + diff --git a/tests/api/specs/dev-portal-helpers/create-team.spec.ts b/tests/api/specs/dev-portal-helpers/create-team.spec.ts new file mode 100644 index 000000000..605d24984 --- /dev/null +++ b/tests/api/specs/dev-portal-helpers/create-team.spec.ts @@ -0,0 +1,127 @@ +import axios from 'axios'; +import { createAppSession } from '../../helpers/auth0'; +import { createTestTeam, createTestUser, deleteTestTeam, deleteTestUser } from '../../helpers/hasura'; + +const INTERNAL_API_URL = process.env.INTERNAL_API_URL; + +describe('Dev Portal Helpers API Endpoints', () => { + describe('POST /api/create-team', () => { + let cleanUpFunctions: Array<() => Promise> = []; + + afterEach(async () => { + await cleanUpFunctions.reduce>( + (promise, callback) => promise.then(() => callback()), + Promise.resolve(), + ); + + cleanUpFunctions = []; + }); + + it('Create Team Successfully For Existing User', async () => { + const userEmail = `existing_${Date.now()}@example.com`; + const testTeamId = await createTestTeam('Initial Team'); + const testUserId = await createTestUser(userEmail, testTeamId); + + // Add cleanup functions in reverse order (users first, then teams due to foreign key constraints) + cleanUpFunctions.push(async () => await deleteTestUser(testUserId)); + cleanUpFunctions.push(async () => await deleteTestTeam(testTeamId)); + + // Generate unique auth0Id to prevent constraint violations + const uniqueAuth0Id = `auth0|test_existing_user_${Date.now()}`; + + const existingUserSession = await createAppSession({ + user: { + sub: uniqueAuth0Id, + email: userEmail, + hasura: { + id: testUserId + } + } + }); + + const teamData = { + team_name: 'Test Team for API Tests', + hasUser: true + }; + + try { + const response = await axios.post( + `${INTERNAL_API_URL}/api/create-team`, + teamData, + { + headers: { + 'Content-Type': 'application/json', + 'Cookie': existingUserSession + } + } + ); + + expect( + response.status, + `Create team request resolved with a wrong code:\n${JSON.stringify(response.data, null, 2)}` + ).toBe(200); + + expect(response.data).toEqual( + expect.objectContaining({ + returnTo: expect.stringMatching(/^\/teams\/team_[a-f0-9]{32}$/) + }) + ); + } catch (error: any) { + throw error; + } + }); + + it('Create Team Successfully For New User', async () => { + const userEmail = `newuser_${Date.now()}@example.com`; + const teamData = { + team_name: 'Test Team for New User', + hasUser: false + }; + + // Generate unique auth0Id to prevent constraint violations + const uniqueAuth0Id = `auth0|test_new_user_${Date.now()}`; + + const auth0Session = { + user: { + sub: uniqueAuth0Id, + email: userEmail, + hasura: { + id: `test_hasura_user_id_${Date.now()}` + } + } + }; + + const sessionCookie = await createAppSession(auth0Session); + + try { + const response = await axios.post( + `${INTERNAL_API_URL}/api/create-team`, + teamData, + { + headers: { + 'Content-Type': 'application/json', + 'Cookie': sessionCookie + } + } + ); + + expect(response.status).toBe(200); + expect(response.data).toEqual( + expect.objectContaining({ + returnTo: expect.stringMatching(/^\/teams\/team_[a-f0-9]{32}\/apps\/$/) + }) + ); + } catch (error: any) { + // Skip test if Ironclad API is not configured (expected in test env) + if (error.response?.status === 500) { + const detail = error.response?.data?.detail || ''; + if (detail.includes('Failed to send acceptance') || + detail.includes('Failed to create team')) { + return; // Skip test - external API not available + } + } + throw error; + } + }); + }); +}); From f4aa2080307bb431dd0107fb2a1b215eaf5ff15c Mon Sep 17 00:00:00 2001 From: mishayouknowme Date: Wed, 10 Sep 2025 13:33:08 +0200 Subject: [PATCH 02/10] add test for create-action --- .../dev-portal-helpers/create-action.spec.ts | 135 ++++++++++++++++++ tests/api/specs/helpers/team.spec.ts | 63 -------- 2 files changed, 135 insertions(+), 63 deletions(-) create mode 100644 tests/api/specs/dev-portal-helpers/create-action.spec.ts delete mode 100644 tests/api/specs/helpers/team.spec.ts diff --git a/tests/api/specs/dev-portal-helpers/create-action.spec.ts b/tests/api/specs/dev-portal-helpers/create-action.spec.ts new file mode 100644 index 000000000..2da461608 --- /dev/null +++ b/tests/api/specs/dev-portal-helpers/create-action.spec.ts @@ -0,0 +1,135 @@ +import axios from 'axios'; +import crypto from 'crypto'; +import { adminGraphqlClient, createTestApp, createTestTeam, createTestUser, deleteTestApiKey, deleteTestApp, deleteTestTeam, deleteTestUser } from '../../helpers/hasura'; + +const INTERNAL_API_URL = process.env.INTERNAL_API_URL; + +// Enhanced version of createTestApiKey that generates real credentials +const createTestApiKeyWithCredentials = async (teamId: string, name: string = "Test API Key") => { + const GENERAL_SECRET_KEY = process.env.GENERAL_SECRET_KEY; + if (!GENERAL_SECRET_KEY) { + throw new Error("GENERAL_SECRET_KEY env var must be set for tests"); + } + + // Step 1: Create API key record to get UUID + const createMutation = ` + mutation CreateApiKey($object: api_key_insert_input!) { + insert_api_key_one(object: $object) { + id + } + } + `; + + // First create with temporary hash to get UUID + const createResponse = (await adminGraphqlClient.request(createMutation, { + object: { + team_id: teamId, + api_key: "temp_hash", + name, + is_active: true + } + })) as any; + + const uuid_id = createResponse.insert_api_key_one?.id; + if (!uuid_id) { + throw new Error("Failed to create API key record"); + } + + // Step 2: Generate real secret and hash using UUID as key_id + const secret = `sk_${crypto.randomBytes(24).toString("hex")}`; + const hmac = crypto.createHmac("sha256", GENERAL_SECRET_KEY); + hmac.update(`${uuid_id}.${secret}`); + const hashed_secret = hmac.digest("hex"); + + // Step 3: Update with real hash + const updateMutation = ` + mutation UpdateApiKey($id: String!, $api_key: String!) { + update_api_key_by_pk(pk_columns: {id: $id}, _set: {api_key: $api_key}) { + id + } + } + `; + + await adminGraphqlClient.request(updateMutation, { + id: uuid_id, + api_key: hashed_secret + }); + + // Step 4: Create proper API key header format for HTTP auth + const credentials = `${uuid_id}:${secret}`; + const encodedCredentials = Buffer.from(credentials).toString('base64'); + const apiKeyHeader = `api_${encodedCredentials}`; + + return { + apiKeyId: uuid_id, + secret, + apiKeyHeader + }; +}; + + +describe('Dev Portal Helpers API Endpoints', () => { + describe('POST /api/v2/create-action/[app_id]', () => { + let cleanUpFunctions: Array<() => Promise> = []; + + afterEach(async () => { + await cleanUpFunctions.reduce>( + (promise, callback) => promise.then(() => callback()), + Promise.resolve(), + ); + + cleanUpFunctions = []; + }); + + it('Create Action Successfully with API Key', async () => { + // Setup test data + const teamId = await createTestTeam('Test Team'); + cleanUpFunctions.push(async () => await deleteTestTeam(teamId)); + + const userEmail = `testuser_${Date.now()}@example.com`; + const userId = await createTestUser(userEmail, teamId); + cleanUpFunctions.push(async () => await deleteTestUser(userId)); + + const appId = await createTestApp('Test App', teamId); + cleanUpFunctions.push(async () => await deleteTestApp(appId)); + + // Create API key for authentication + const { apiKeyId, apiKeyHeader } = await createTestApiKeyWithCredentials(teamId, "Test Key for Create Action"); + cleanUpFunctions.push(async () => await deleteTestApiKey(apiKeyId)); + + // Test data + const actionData = { + action: `test_action_${Date.now()}`, + name: 'Test Action', + description: 'Test action description', + max_verifications: 5 + }; + + const response = await axios.post( + `${INTERNAL_API_URL}/api/v2/create-action/${appId}`, + actionData, + { + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKeyHeader}` + } + } + ); + + expect(response.status).toBe(200); + expect(response.data).toEqual( + expect.objectContaining({ + action: expect.objectContaining({ + id: expect.any(String), + action: actionData.action, + name: actionData.name, + description: actionData.description, + max_verifications: actionData.max_verifications, + external_nullifier: expect.any(String), + status: 'active' + }) + }) + ); + }); + }); +}); diff --git a/tests/api/specs/helpers/team.spec.ts b/tests/api/specs/helpers/team.spec.ts deleted file mode 100644 index 1f3112766..000000000 --- a/tests/api/specs/helpers/team.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -import axios from "axios"; -import { deleteTestTeam } from "helpers"; -import { createAppSession } from "helpers/auth0"; - -describe.skip("Team Actions", () => { - const INTERNAL_API_URL = process.env.INTERNAL_API_URL; - let cleanUpFunctions: Array<() => Promise> = []; - let sessionCookie: string; - - beforeAll(async () => { - if (!INTERNAL_API_URL) { - throw new Error("INTERNAL_API_URL environment variable is not set!"); - } - - sessionCookie = await createAppSession({ - user: { - sub: process.env.TEST_USER_AUTH0_ID, - hasura: { - id: process.env.TEST_USER_HASURA_ID, - }, - }, - }); - }); - - afterEach(async () => { - await cleanUpFunctions.reduce>( - (promise, callback) => promise.then(() => callback()), - Promise.resolve() - ); - - cleanUpFunctions = []; - }); - - it("Create a team", async () => { - const response = await axios.post( - `${INTERNAL_API_URL}/api/create-team`, - { - team_name: "My team 1", - hasUser: true, - }, - { - headers: { - "Content-Type": "application/json", - Cookie: sessionCookie, - }, - validateStatus: () => true, - } - ); - - const body = response.data; - expect( - response.status, - `Create Team response body: ${JSON.stringify(body)}` - ).toBe(200); - - if (body.returnTo && typeof body.returnTo === "string") { - cleanUpFunctions.push(async () => { - const teamId = response.data.returnTo.split("/teams/")[1]; - await deleteTestTeam(teamId); - }); - } - }); -}); From 863761c3f54251bb689555df6cf92c6c4163b9ca Mon Sep 17 00:00:00 2001 From: mishayouknowme Date: Wed, 10 Sep 2025 14:27:24 +0200 Subject: [PATCH 03/10] Add test for graphql proxy --- tests/api/helpers/hasura.ts | 54 ++++++++ .../dev-portal-helpers/create-action.spec.ts | 67 +--------- .../dev-portal-helpers/graphql-proxy.spec.ts | 124 ++++++++++++++++++ 3 files changed, 179 insertions(+), 66 deletions(-) create mode 100644 tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts diff --git a/tests/api/helpers/hasura.ts b/tests/api/helpers/hasura.ts index 9a7d23454..48e34a61d 100644 --- a/tests/api/helpers/hasura.ts +++ b/tests/api/helpers/hasura.ts @@ -501,6 +501,60 @@ export const deleteTestApiKey = async (apiKeyId: string) => { } }; +// Enhanced version of createTestApiKey that generates real credentials for HTTP auth +export const createTestApiKeyWithCredentials = async (teamId: string, name: string = "Test API Key") => { + const GENERAL_SECRET_KEY = process.env.GENERAL_SECRET_KEY; + if (!GENERAL_SECRET_KEY) { + throw new Error("GENERAL_SECRET_KEY env var must be set for tests"); + } + + // Step 1: Create API key record to get UUID + const createResponse = (await adminGraphqlClient.request(CREATE_API_KEY_MUTATION, { + object: { + team_id: teamId, + api_key: "temp_hash", // Temporary hash, will be updated + name, + is_active: true + } + })) as any; + + const uuid_id = createResponse.insert_api_key_one?.id; + if (!uuid_id) { + throw new Error("Failed to create API key record"); + } + + // Step 2: Generate real secret and hash using UUID as key_id + const secret = `sk_${require('crypto').randomBytes(24).toString("hex")}`; + const hmac = require('crypto').createHmac("sha256", GENERAL_SECRET_KEY); + hmac.update(`${uuid_id}.${secret}`); + const hashed_secret = hmac.digest("hex"); + + // Step 3: Update with real hash + const updateMutation = ` + mutation UpdateApiKey($id: String!, $api_key: String!) { + update_api_key_by_pk(pk_columns: {id: $id}, _set: {api_key: $api_key}) { + id + } + } + `; + + await adminGraphqlClient.request(updateMutation, { + id: uuid_id, + api_key: hashed_secret + }); + + // Step 4: Create proper API key header format for HTTP auth + const credentials = `${uuid_id}:${secret}`; + const encodedCredentials = Buffer.from(credentials).toString('base64'); + const apiKeyHeader = `api_${encodedCredentials}`; + + return { + apiKeyId: uuid_id, + secret, + apiKeyHeader + }; +}; + // Helper for creating test action export const createTestAction = async ( appId: string, diff --git a/tests/api/specs/dev-portal-helpers/create-action.spec.ts b/tests/api/specs/dev-portal-helpers/create-action.spec.ts index 2da461608..3fdcf5980 100644 --- a/tests/api/specs/dev-portal-helpers/create-action.spec.ts +++ b/tests/api/specs/dev-portal-helpers/create-action.spec.ts @@ -1,73 +1,8 @@ import axios from 'axios'; -import crypto from 'crypto'; -import { adminGraphqlClient, createTestApp, createTestTeam, createTestUser, deleteTestApiKey, deleteTestApp, deleteTestTeam, deleteTestUser } from '../../helpers/hasura'; +import { createTestApiKeyWithCredentials, createTestApp, createTestTeam, createTestUser, deleteTestApiKey, deleteTestApp, deleteTestTeam, deleteTestUser } from '../../helpers/hasura'; const INTERNAL_API_URL = process.env.INTERNAL_API_URL; -// Enhanced version of createTestApiKey that generates real credentials -const createTestApiKeyWithCredentials = async (teamId: string, name: string = "Test API Key") => { - const GENERAL_SECRET_KEY = process.env.GENERAL_SECRET_KEY; - if (!GENERAL_SECRET_KEY) { - throw new Error("GENERAL_SECRET_KEY env var must be set for tests"); - } - - // Step 1: Create API key record to get UUID - const createMutation = ` - mutation CreateApiKey($object: api_key_insert_input!) { - insert_api_key_one(object: $object) { - id - } - } - `; - - // First create with temporary hash to get UUID - const createResponse = (await adminGraphqlClient.request(createMutation, { - object: { - team_id: teamId, - api_key: "temp_hash", - name, - is_active: true - } - })) as any; - - const uuid_id = createResponse.insert_api_key_one?.id; - if (!uuid_id) { - throw new Error("Failed to create API key record"); - } - - // Step 2: Generate real secret and hash using UUID as key_id - const secret = `sk_${crypto.randomBytes(24).toString("hex")}`; - const hmac = crypto.createHmac("sha256", GENERAL_SECRET_KEY); - hmac.update(`${uuid_id}.${secret}`); - const hashed_secret = hmac.digest("hex"); - - // Step 3: Update with real hash - const updateMutation = ` - mutation UpdateApiKey($id: String!, $api_key: String!) { - update_api_key_by_pk(pk_columns: {id: $id}, _set: {api_key: $api_key}) { - id - } - } - `; - - await adminGraphqlClient.request(updateMutation, { - id: uuid_id, - api_key: hashed_secret - }); - - // Step 4: Create proper API key header format for HTTP auth - const credentials = `${uuid_id}:${secret}`; - const encodedCredentials = Buffer.from(credentials).toString('base64'); - const apiKeyHeader = `api_${encodedCredentials}`; - - return { - apiKeyId: uuid_id, - secret, - apiKeyHeader - }; -}; - - describe('Dev Portal Helpers API Endpoints', () => { describe('POST /api/v2/create-action/[app_id]', () => { let cleanUpFunctions: Array<() => Promise> = []; diff --git a/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts b/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts new file mode 100644 index 000000000..567ad8dee --- /dev/null +++ b/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts @@ -0,0 +1,124 @@ +import axios from 'axios'; +import { createAppSession } from '../../helpers/auth0'; +import { createTestApiKeyWithCredentials, createTestApp, createTestTeam, createTestUser, deleteTestApiKey, deleteTestApp, deleteTestTeam, deleteTestUser } from '../../helpers/hasura'; + +const INTERNAL_API_URL = process.env.INTERNAL_API_URL; +const INTERNAL_ENDPOINTS_SECRET = process.env.INTERNAL_ENDPOINTS_SECRET; + +describe('Dev Portal Helpers API Endpoints', () => { + describe('POST /api/v1/graphql', () => { + let cleanUpFunctions: Array<() => Promise> = []; + + afterEach(async () => { + await cleanUpFunctions.reduce>( + (promise, callback) => promise.then(() => callback()), + Promise.resolve(), + ); + + cleanUpFunctions = []; + }); + + describe('API Key Authentication', () => { + it('Should authenticate with API key and proxy GraphQL request', async () => { + // Setup test data + const teamId = await createTestTeam('Test Team GraphQL'); + cleanUpFunctions.push(async () => await deleteTestTeam(teamId)); + + const userEmail = `testuser_${Date.now()}@example.com`; + const userId = await createTestUser(userEmail, teamId); + cleanUpFunctions.push(async () => await deleteTestUser(userId)); + + const appId = await createTestApp('Test App GraphQL', teamId); + cleanUpFunctions.push(async () => await deleteTestApp(appId)); + + // Create API key for authentication + const { apiKeyId, apiKeyHeader } = await createTestApiKeyWithCredentials(teamId, "Test Key for GraphQL"); + cleanUpFunctions.push(async () => await deleteTestApiKey(apiKeyId)); + + // Test GraphQL query - simple query to get teams + const graphqlQuery = { + query: ` + query GetTeams { + team(limit: 1) { + id + } + } + ` + }; + + const response = await axios.post( + `${INTERNAL_API_URL}/api/v1/graphql`, + graphqlQuery, + { + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKeyHeader}` + } + } + ); + + expect(response.status).toBe(200); + const returnedTeamIds = response.data.data.team.map((team: any) => team.id); + expect(returnedTeamIds).toContain(teamId); + }); + }); + + describe('Auth0 Session Authentication', () => { + it('Should authenticate with Auth0 session cookie and proxy GraphQL request', async () => { + // Setup test data for Auth0 user + const teamId = await createTestTeam('Test Team Auth0 GraphQL'); + cleanUpFunctions.push(async () => await deleteTestTeam(teamId)); + + const userEmail = `auth0user_${Date.now()}@example.com`; + const userId = await createTestUser(userEmail, teamId); + cleanUpFunctions.push(async () => await deleteTestUser(userId)); + + // Create Auth0 session + const mockAuth0Session = { + user: { + sub: `auth0|test_${Date.now()}`, + hasura: { + id: userId + } + } + }; + + const sessionCookie = await createAppSession(mockAuth0Session); + + // Test GraphQL query with Auth0 session + const graphqlQuery = { + query: ` + query GetUser($userId: String!) { + user_by_pk(id: $userId) { + id + email + } + } + `, + variables: { + userId: userId + } + }; + + const response = await axios.post( + `${INTERNAL_API_URL}/api/v1/graphql`, + graphqlQuery, + { + headers: { + 'Content-Type': 'application/json', + 'Cookie': sessionCookie + } + } + ); + + expect(response.status).toBe(200); + expect(response.data).toHaveProperty('data'); + expect(response.data.data).toHaveProperty('user_by_pk'); + expect(response.data.data.user_by_pk).toMatchObject({ + id: userId, + email: userEmail + }); + }); + }); + }); +}); From a86dec3cdf0ed126e80f679dd72d58a25ec57ddb Mon Sep 17 00:00:00 2001 From: mishayouknowme Date: Wed, 10 Sep 2025 15:09:25 +0200 Subject: [PATCH 04/10] Remove helper --- tests/api/helpers/auth0/index.ts | 1 - tests/api/helpers/hasura.ts | 70 ++++++------------- .../dev-portal-helpers/create-action.spec.ts | 4 +- .../dev-portal-helpers/graphql-proxy.spec.ts | 4 +- tests/api/specs/hasura/reset-api-key.spec.ts | 3 +- 5 files changed, 28 insertions(+), 54 deletions(-) diff --git a/tests/api/helpers/auth0/index.ts b/tests/api/helpers/auth0/index.ts index 70917ae98..363d4c5da 100644 --- a/tests/api/helpers/auth0/index.ts +++ b/tests/api/helpers/auth0/index.ts @@ -1,2 +1 @@ export * from "./create-app-session"; - diff --git a/tests/api/helpers/hasura.ts b/tests/api/helpers/hasura.ts index 48e34a61d..f4427ad70 100644 --- a/tests/api/helpers/hasura.ts +++ b/tests/api/helpers/hasura.ts @@ -455,54 +455,8 @@ export const deleteTestLocalisation = async (localisationId: string) => { } }; -// Helper for creating test API key -export const createTestApiKey = async ( - teamId: string, - name: string = "Test API Key" -) => { - try { - const response = (await adminGraphqlClient.request( - CREATE_API_KEY_MUTATION, - { - object: { - team_id: teamId, - name, - api_key: "test_hashed_secret_value", - is_active: true, - }, - } - )) as any; - - return response.insert_api_key_one?.id; - } catch (error: any) { - const errorMessage = - error?.response?.data?.message || error?.message || "Unknown error"; - throw new Error(`Failed to create test API key: ${errorMessage}`); - } -}; - -// Helper for deleting test API key -export const deleteTestApiKey = async (apiKeyId: string) => { - try { - const response = (await adminGraphqlClient.request( - DELETE_API_KEY_MUTATION, - { - id: apiKeyId, - } - )) as any; - - return response.delete_api_key_by_pk?.id; - } catch (error: any) { - const errorMessage = - error?.response?.data?.message || error?.message || "Unknown error"; - throw new Error( - `Failed to delete test API key ${apiKeyId}: ${errorMessage}` - ); - } -}; - -// Enhanced version of createTestApiKey that generates real credentials for HTTP auth -export const createTestApiKeyWithCredentials = async (teamId: string, name: string = "Test API Key") => { +// Helper for creating test API key with real credentials for HTTP auth +export const createTestApiKey = async (teamId: string, name: string = "Test API Key") => { const GENERAL_SECRET_KEY = process.env.GENERAL_SECRET_KEY; if (!GENERAL_SECRET_KEY) { throw new Error("GENERAL_SECRET_KEY env var must be set for tests"); @@ -555,6 +509,26 @@ export const createTestApiKeyWithCredentials = async (teamId: string, name: stri }; }; +// Helper for deleting test API key +export const deleteTestApiKey = async (apiKeyId: string) => { + try { + const response = (await adminGraphqlClient.request( + DELETE_API_KEY_MUTATION, + { + id: apiKeyId, + } + )) as any; + + return response.delete_api_key_by_pk?.id; + } catch (error: any) { + const errorMessage = + error?.response?.data?.message || error?.message || "Unknown error"; + throw new Error( + `Failed to delete test API key ${apiKeyId}: ${errorMessage}` + ); + } +}; + // Helper for creating test action export const createTestAction = async ( appId: string, diff --git a/tests/api/specs/dev-portal-helpers/create-action.spec.ts b/tests/api/specs/dev-portal-helpers/create-action.spec.ts index 3fdcf5980..de55123f6 100644 --- a/tests/api/specs/dev-portal-helpers/create-action.spec.ts +++ b/tests/api/specs/dev-portal-helpers/create-action.spec.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import { createTestApiKeyWithCredentials, createTestApp, createTestTeam, createTestUser, deleteTestApiKey, deleteTestApp, deleteTestTeam, deleteTestUser } from '../../helpers/hasura'; +import { createTestApiKey, createTestApp, createTestTeam, createTestUser, deleteTestApiKey, deleteTestApp, deleteTestTeam, deleteTestUser } from '../../helpers/hasura'; const INTERNAL_API_URL = process.env.INTERNAL_API_URL; @@ -29,7 +29,7 @@ describe('Dev Portal Helpers API Endpoints', () => { cleanUpFunctions.push(async () => await deleteTestApp(appId)); // Create API key for authentication - const { apiKeyId, apiKeyHeader } = await createTestApiKeyWithCredentials(teamId, "Test Key for Create Action"); + const { apiKeyId, apiKeyHeader } = await createTestApiKey(teamId, "Test Key for Create Action"); cleanUpFunctions.push(async () => await deleteTestApiKey(apiKeyId)); // Test data diff --git a/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts b/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts index 567ad8dee..4802c0c59 100644 --- a/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts +++ b/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { createAppSession } from '../../helpers/auth0'; -import { createTestApiKeyWithCredentials, createTestApp, createTestTeam, createTestUser, deleteTestApiKey, deleteTestApp, deleteTestTeam, deleteTestUser } from '../../helpers/hasura'; +import { createTestApiKey, createTestApp, createTestTeam, createTestUser, deleteTestApiKey, deleteTestApp, deleteTestTeam, deleteTestUser } from '../../helpers/hasura'; const INTERNAL_API_URL = process.env.INTERNAL_API_URL; const INTERNAL_ENDPOINTS_SECRET = process.env.INTERNAL_ENDPOINTS_SECRET; @@ -32,7 +32,7 @@ describe('Dev Portal Helpers API Endpoints', () => { cleanUpFunctions.push(async () => await deleteTestApp(appId)); // Create API key for authentication - const { apiKeyId, apiKeyHeader } = await createTestApiKeyWithCredentials(teamId, "Test Key for GraphQL"); + const { apiKeyId, apiKeyHeader } = await createTestApiKey(teamId, "Test Key for GraphQL"); cleanUpFunctions.push(async () => await deleteTestApiKey(apiKeyId)); // Test GraphQL query - simple query to get teams diff --git a/tests/api/specs/hasura/reset-api-key.spec.ts b/tests/api/specs/hasura/reset-api-key.spec.ts index d6404c7e4..ed2cef17f 100644 --- a/tests/api/specs/hasura/reset-api-key.spec.ts +++ b/tests/api/specs/hasura/reset-api-key.spec.ts @@ -63,10 +63,11 @@ describe("Hasura API - Reset API Key", () => { testMetadataId = metadata.id; // Create test API key - testApiKeyId = await createTestApiKey( + const apiKeyData = await createTestApiKey( testTeamId!, "Test API Key for Reset" ); + testApiKeyId = apiKeyData.apiKeyId; }); it("Reset API Key Successfully", async () => { From 195355c1a496ef87a972d8242e59b801e39424d6 Mon Sep 17 00:00:00 2001 From: mishayouknowme Date: Wed, 10 Sep 2025 15:48:31 +0200 Subject: [PATCH 05/10] Fix format issues --- tests/api/helpers/hasura.ts | 50 +++++++----- .../dev-portal-helpers/create-action.spec.ts | 50 +++++++----- .../dev-portal-helpers/create-team.spec.ts | 81 ++++++++++--------- .../dev-portal-helpers/graphql-proxy.spec.ts | 78 ++++++++++-------- 4 files changed, 150 insertions(+), 109 deletions(-) diff --git a/tests/api/helpers/hasura.ts b/tests/api/helpers/hasura.ts index a3910fc6c..c3d973046 100644 --- a/tests/api/helpers/hasura.ts +++ b/tests/api/helpers/hasura.ts @@ -456,33 +456,39 @@ export const deleteTestLocalisation = async (localisationId: string) => { }; // Helper for creating test API key with real credentials for HTTP auth -export const createTestApiKey = async (teamId: string, name: string = "Test API Key") => { +export const createTestApiKey = async ( + teamId: string, + name: string = "Test API Key", +) => { const GENERAL_SECRET_KEY = process.env.GENERAL_SECRET_KEY; if (!GENERAL_SECRET_KEY) { throw new Error("GENERAL_SECRET_KEY env var must be set for tests"); } - - // Step 1: Create API key record to get UUID - const createResponse = (await adminGraphqlClient.request(CREATE_API_KEY_MUTATION, { - object: { - team_id: teamId, - api_key: "temp_hash", // Temporary hash, will be updated - name, - is_active: true - } - })) as any; - + + // Step 1: Create API key record to get UUID + const createResponse = (await adminGraphqlClient.request( + CREATE_API_KEY_MUTATION, + { + object: { + team_id: teamId, + api_key: "temp_hash", // Temporary hash, will be updated + name, + is_active: true, + }, + }, + )) as any; + const uuid_id = createResponse.insert_api_key_one?.id; if (!uuid_id) { throw new Error("Failed to create API key record"); } - + // Step 2: Generate real secret and hash using UUID as key_id - const secret = `sk_${require('crypto').randomBytes(24).toString("hex")}`; - const hmac = require('crypto').createHmac("sha256", GENERAL_SECRET_KEY); + const secret = `sk_${require("crypto").randomBytes(24).toString("hex")}`; + const hmac = require("crypto").createHmac("sha256", GENERAL_SECRET_KEY); hmac.update(`${uuid_id}.${secret}`); const hashed_secret = hmac.digest("hex"); - + // Step 3: Update with real hash const updateMutation = ` mutation UpdateApiKey($id: String!, $api_key: String!) { @@ -491,21 +497,21 @@ export const createTestApiKey = async (teamId: string, name: string = "Test API } } `; - + await adminGraphqlClient.request(updateMutation, { id: uuid_id, - api_key: hashed_secret + api_key: hashed_secret, }); - + // Step 4: Create proper API key header format for HTTP auth const credentials = `${uuid_id}:${secret}`; - const encodedCredentials = Buffer.from(credentials).toString('base64'); + const encodedCredentials = Buffer.from(credentials).toString("base64"); const apiKeyHeader = `api_${encodedCredentials}`; - + return { apiKeyId: uuid_id, secret, - apiKeyHeader + apiKeyHeader, }; }; diff --git a/tests/api/specs/dev-portal-helpers/create-action.spec.ts b/tests/api/specs/dev-portal-helpers/create-action.spec.ts index de55123f6..aefee8dab 100644 --- a/tests/api/specs/dev-portal-helpers/create-action.spec.ts +++ b/tests/api/specs/dev-portal-helpers/create-action.spec.ts @@ -1,10 +1,19 @@ -import axios from 'axios'; -import { createTestApiKey, createTestApp, createTestTeam, createTestUser, deleteTestApiKey, deleteTestApp, deleteTestTeam, deleteTestUser } from '../../helpers/hasura'; +import axios from "axios"; +import { + createTestApiKey, + createTestApp, + createTestTeam, + createTestUser, + deleteTestApiKey, + deleteTestApp, + deleteTestTeam, + deleteTestUser, +} from "../../helpers/hasura"; const INTERNAL_API_URL = process.env.INTERNAL_API_URL; -describe('Dev Portal Helpers API Endpoints', () => { - describe('POST /api/v2/create-action/[app_id]', () => { +describe("Dev Portal Helpers API Endpoints", () => { + describe("POST /api/v2/create-action/[app_id]", () => { let cleanUpFunctions: Array<() => Promise> = []; afterEach(async () => { @@ -16,28 +25,31 @@ describe('Dev Portal Helpers API Endpoints', () => { cleanUpFunctions = []; }); - it('Create Action Successfully with API Key', async () => { + it("Create Action Successfully with API Key", async () => { // Setup test data - const teamId = await createTestTeam('Test Team'); + const teamId = await createTestTeam("Test Team"); cleanUpFunctions.push(async () => await deleteTestTeam(teamId)); - + const userEmail = `testuser_${Date.now()}@example.com`; const userId = await createTestUser(userEmail, teamId); cleanUpFunctions.push(async () => await deleteTestUser(userId)); - const appId = await createTestApp('Test App', teamId); + const appId = await createTestApp("Test App", teamId); cleanUpFunctions.push(async () => await deleteTestApp(appId)); // Create API key for authentication - const { apiKeyId, apiKeyHeader } = await createTestApiKey(teamId, "Test Key for Create Action"); + const { apiKeyId, apiKeyHeader } = await createTestApiKey( + teamId, + "Test Key for Create Action", + ); cleanUpFunctions.push(async () => await deleteTestApiKey(apiKeyId)); // Test data const actionData = { action: `test_action_${Date.now()}`, - name: 'Test Action', - description: 'Test action description', - max_verifications: 5 + name: "Test Action", + description: "Test action description", + max_verifications: 5, }; const response = await axios.post( @@ -45,10 +57,10 @@ describe('Dev Portal Helpers API Endpoints', () => { actionData, { headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKeyHeader}` - } - } + "Content-Type": "application/json", + Authorization: `Bearer ${apiKeyHeader}`, + }, + }, ); expect(response.status).toBe(200); @@ -61,9 +73,9 @@ describe('Dev Portal Helpers API Endpoints', () => { description: actionData.description, max_verifications: actionData.max_verifications, external_nullifier: expect.any(String), - status: 'active' - }) - }) + status: "active", + }), + }), ); }); }); diff --git a/tests/api/specs/dev-portal-helpers/create-team.spec.ts b/tests/api/specs/dev-portal-helpers/create-team.spec.ts index 605d24984..cf3d7119b 100644 --- a/tests/api/specs/dev-portal-helpers/create-team.spec.ts +++ b/tests/api/specs/dev-portal-helpers/create-team.spec.ts @@ -1,11 +1,16 @@ -import axios from 'axios'; -import { createAppSession } from '../../helpers/auth0'; -import { createTestTeam, createTestUser, deleteTestTeam, deleteTestUser } from '../../helpers/hasura'; +import axios from "axios"; +import { createAppSession } from "../../helpers/auth0"; +import { + createTestTeam, + createTestUser, + deleteTestTeam, + deleteTestUser, +} from "../../helpers/hasura"; const INTERNAL_API_URL = process.env.INTERNAL_API_URL; -describe('Dev Portal Helpers API Endpoints', () => { - describe('POST /api/create-team', () => { +describe("Dev Portal Helpers API Endpoints", () => { + describe("POST /api/create-team", () => { let cleanUpFunctions: Array<() => Promise> = []; afterEach(async () => { @@ -17,11 +22,11 @@ describe('Dev Portal Helpers API Endpoints', () => { cleanUpFunctions = []; }); - it('Create Team Successfully For Existing User', async () => { + it("Create Team Successfully For Existing User", async () => { const userEmail = `existing_${Date.now()}@example.com`; - const testTeamId = await createTestTeam('Initial Team'); + const testTeamId = await createTestTeam("Initial Team"); const testUserId = await createTestUser(userEmail, testTeamId); - + // Add cleanup functions in reverse order (users first, then teams due to foreign key constraints) cleanUpFunctions.push(async () => await deleteTestUser(testUserId)); cleanUpFunctions.push(async () => await deleteTestTeam(testTeamId)); @@ -34,14 +39,14 @@ describe('Dev Portal Helpers API Endpoints', () => { sub: uniqueAuth0Id, email: userEmail, hasura: { - id: testUserId - } - } + id: testUserId, + }, + }, }); const teamData = { - team_name: 'Test Team for API Tests', - hasUser: true + team_name: "Test Team for API Tests", + hasUser: true, }; try { @@ -50,45 +55,45 @@ describe('Dev Portal Helpers API Endpoints', () => { teamData, { headers: { - 'Content-Type': 'application/json', - 'Cookie': existingUserSession - } - } + "Content-Type": "application/json", + Cookie: existingUserSession, + }, + }, ); expect( response.status, - `Create team request resolved with a wrong code:\n${JSON.stringify(response.data, null, 2)}` + `Create team request resolved with a wrong code:\n${JSON.stringify(response.data, null, 2)}`, ).toBe(200); expect(response.data).toEqual( expect.objectContaining({ - returnTo: expect.stringMatching(/^\/teams\/team_[a-f0-9]{32}$/) - }) + returnTo: expect.stringMatching(/^\/teams\/team_[a-f0-9]{32}$/), + }), ); } catch (error: any) { throw error; } }); - it('Create Team Successfully For New User', async () => { + it("Create Team Successfully For New User", async () => { const userEmail = `newuser_${Date.now()}@example.com`; const teamData = { - team_name: 'Test Team for New User', - hasUser: false + team_name: "Test Team for New User", + hasUser: false, }; // Generate unique auth0Id to prevent constraint violations const uniqueAuth0Id = `auth0|test_new_user_${Date.now()}`; - + const auth0Session = { user: { sub: uniqueAuth0Id, email: userEmail, hasura: { - id: `test_hasura_user_id_${Date.now()}` - } - } + id: `test_hasura_user_id_${Date.now()}`, + }, + }, }; const sessionCookie = await createAppSession(auth0Session); @@ -99,24 +104,28 @@ describe('Dev Portal Helpers API Endpoints', () => { teamData, { headers: { - 'Content-Type': 'application/json', - 'Cookie': sessionCookie - } - } + "Content-Type": "application/json", + Cookie: sessionCookie, + }, + }, ); expect(response.status).toBe(200); expect(response.data).toEqual( expect.objectContaining({ - returnTo: expect.stringMatching(/^\/teams\/team_[a-f0-9]{32}\/apps\/$/) - }) + returnTo: expect.stringMatching( + /^\/teams\/team_[a-f0-9]{32}\/apps\/$/, + ), + }), ); } catch (error: any) { // Skip test if Ironclad API is not configured (expected in test env) if (error.response?.status === 500) { - const detail = error.response?.data?.detail || ''; - if (detail.includes('Failed to send acceptance') || - detail.includes('Failed to create team')) { + const detail = error.response?.data?.detail || ""; + if ( + detail.includes("Failed to send acceptance") || + detail.includes("Failed to create team") + ) { return; // Skip test - external API not available } } diff --git a/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts b/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts index 4802c0c59..cd4e2a20f 100644 --- a/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts +++ b/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts @@ -1,12 +1,21 @@ -import axios from 'axios'; -import { createAppSession } from '../../helpers/auth0'; -import { createTestApiKey, createTestApp, createTestTeam, createTestUser, deleteTestApiKey, deleteTestApp, deleteTestTeam, deleteTestUser } from '../../helpers/hasura'; +import axios from "axios"; +import { createAppSession } from "../../helpers/auth0"; +import { + createTestApiKey, + createTestApp, + createTestTeam, + createTestUser, + deleteTestApiKey, + deleteTestApp, + deleteTestTeam, + deleteTestUser, +} from "../../helpers/hasura"; const INTERNAL_API_URL = process.env.INTERNAL_API_URL; const INTERNAL_ENDPOINTS_SECRET = process.env.INTERNAL_ENDPOINTS_SECRET; -describe('Dev Portal Helpers API Endpoints', () => { - describe('POST /api/v1/graphql', () => { +describe("Dev Portal Helpers API Endpoints", () => { + describe("POST /api/v1/graphql", () => { let cleanUpFunctions: Array<() => Promise> = []; afterEach(async () => { @@ -18,21 +27,24 @@ describe('Dev Portal Helpers API Endpoints', () => { cleanUpFunctions = []; }); - describe('API Key Authentication', () => { - it('Should authenticate with API key and proxy GraphQL request', async () => { + describe("API Key Authentication", () => { + it("Should authenticate with API key and proxy GraphQL request", async () => { // Setup test data - const teamId = await createTestTeam('Test Team GraphQL'); + const teamId = await createTestTeam("Test Team GraphQL"); cleanUpFunctions.push(async () => await deleteTestTeam(teamId)); - + const userEmail = `testuser_${Date.now()}@example.com`; const userId = await createTestUser(userEmail, teamId); cleanUpFunctions.push(async () => await deleteTestUser(userId)); - const appId = await createTestApp('Test App GraphQL', teamId); + const appId = await createTestApp("Test App GraphQL", teamId); cleanUpFunctions.push(async () => await deleteTestApp(appId)); // Create API key for authentication - const { apiKeyId, apiKeyHeader } = await createTestApiKey(teamId, "Test Key for GraphQL"); + const { apiKeyId, apiKeyHeader } = await createTestApiKey( + teamId, + "Test Key for GraphQL", + ); cleanUpFunctions.push(async () => await deleteTestApiKey(apiKeyId)); // Test GraphQL query - simple query to get teams @@ -43,7 +55,7 @@ describe('Dev Portal Helpers API Endpoints', () => { id } } - ` + `, }; const response = await axios.post( @@ -51,22 +63,24 @@ describe('Dev Portal Helpers API Endpoints', () => { graphqlQuery, { headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKeyHeader}` - } - } + "Content-Type": "application/json", + Authorization: `Bearer ${apiKeyHeader}`, + }, + }, ); expect(response.status).toBe(200); - const returnedTeamIds = response.data.data.team.map((team: any) => team.id); + const returnedTeamIds = response.data.data.team.map( + (team: any) => team.id, + ); expect(returnedTeamIds).toContain(teamId); }); }); - describe('Auth0 Session Authentication', () => { - it('Should authenticate with Auth0 session cookie and proxy GraphQL request', async () => { + describe("Auth0 Session Authentication", () => { + it("Should authenticate with Auth0 session cookie and proxy GraphQL request", async () => { // Setup test data for Auth0 user - const teamId = await createTestTeam('Test Team Auth0 GraphQL'); + const teamId = await createTestTeam("Test Team Auth0 GraphQL"); cleanUpFunctions.push(async () => await deleteTestTeam(teamId)); const userEmail = `auth0user_${Date.now()}@example.com`; @@ -78,9 +92,9 @@ describe('Dev Portal Helpers API Endpoints', () => { user: { sub: `auth0|test_${Date.now()}`, hasura: { - id: userId - } - } + id: userId, + }, + }, }; const sessionCookie = await createAppSession(mockAuth0Session); @@ -96,8 +110,8 @@ describe('Dev Portal Helpers API Endpoints', () => { } `, variables: { - userId: userId - } + userId: userId, + }, }; const response = await axios.post( @@ -105,18 +119,18 @@ describe('Dev Portal Helpers API Endpoints', () => { graphqlQuery, { headers: { - 'Content-Type': 'application/json', - 'Cookie': sessionCookie - } - } + "Content-Type": "application/json", + Cookie: sessionCookie, + }, + }, ); expect(response.status).toBe(200); - expect(response.data).toHaveProperty('data'); - expect(response.data.data).toHaveProperty('user_by_pk'); + expect(response.data).toHaveProperty("data"); + expect(response.data.data).toHaveProperty("user_by_pk"); expect(response.data.data.user_by_pk).toMatchObject({ id: userId, - email: userEmail + email: userEmail, }); }); }); From 509f073e839b4b8ea011569d5ce32f84069c3b2e Mon Sep 17 00:00:00 2001 From: mishayouknowme Date: Wed, 10 Sep 2025 15:52:32 +0200 Subject: [PATCH 06/10] Update env.development --- tests/api/.env.development | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/api/.env.development b/tests/api/.env.development index a7f716cdf..d2176a918 100644 --- a/tests/api/.env.development +++ b/tests/api/.env.development @@ -20,3 +20,5 @@ AWS_REGION=eu-west-1 AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= +GENERAL_SECRET_KEY= + From db4750526627fee7ff8f23851e737401730398dc Mon Sep 17 00:00:00 2001 From: mishayouknowme Date: Wed, 10 Sep 2025 15:57:35 +0200 Subject: [PATCH 07/10] Update imports --- .../dev-portal-helpers/create-action.spec.ts | 2 +- .../dev-portal-helpers/create-team.spec.ts | 4 ++-- .../dev-portal-helpers/graphql-proxy.spec.ts | 20 +++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/api/specs/dev-portal-helpers/create-action.spec.ts b/tests/api/specs/dev-portal-helpers/create-action.spec.ts index aefee8dab..5fe482801 100644 --- a/tests/api/specs/dev-portal-helpers/create-action.spec.ts +++ b/tests/api/specs/dev-portal-helpers/create-action.spec.ts @@ -8,7 +8,7 @@ import { deleteTestApp, deleteTestTeam, deleteTestUser, -} from "../../helpers/hasura"; +} from "helpers"; const INTERNAL_API_URL = process.env.INTERNAL_API_URL; diff --git a/tests/api/specs/dev-portal-helpers/create-team.spec.ts b/tests/api/specs/dev-portal-helpers/create-team.spec.ts index cf3d7119b..db8e6ec0a 100644 --- a/tests/api/specs/dev-portal-helpers/create-team.spec.ts +++ b/tests/api/specs/dev-portal-helpers/create-team.spec.ts @@ -1,11 +1,11 @@ import axios from "axios"; -import { createAppSession } from "../../helpers/auth0"; import { createTestTeam, createTestUser, deleteTestTeam, deleteTestUser, -} from "../../helpers/hasura"; +} from "helpers"; +import { createAppSession } from "helpers/auth0"; const INTERNAL_API_URL = process.env.INTERNAL_API_URL; diff --git a/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts b/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts index cd4e2a20f..22206ae24 100644 --- a/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts +++ b/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts @@ -1,15 +1,15 @@ import axios from "axios"; -import { createAppSession } from "../../helpers/auth0"; import { - createTestApiKey, - createTestApp, - createTestTeam, - createTestUser, - deleteTestApiKey, - deleteTestApp, - deleteTestTeam, - deleteTestUser, -} from "../../helpers/hasura"; + createTestApiKey, + createTestApp, + createTestTeam, + createTestUser, + deleteTestApiKey, + deleteTestApp, + deleteTestTeam, + deleteTestUser, +} from "helpers"; +import { createAppSession } from "helpers/auth0"; const INTERNAL_API_URL = process.env.INTERNAL_API_URL; const INTERNAL_ENDPOINTS_SECRET = process.env.INTERNAL_ENDPOINTS_SECRET; From 4fa122cfcb9ab77bb064bb5064090ffaccdda731 Mon Sep 17 00:00:00 2001 From: mishayouknowme Date: Wed, 10 Sep 2025 16:03:25 +0200 Subject: [PATCH 08/10] Fix formatting --- .../dev-portal-helpers/graphql-proxy.spec.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts b/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts index 22206ae24..632c1fb0d 100644 --- a/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts +++ b/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts @@ -1,13 +1,13 @@ import axios from "axios"; import { - createTestApiKey, - createTestApp, - createTestTeam, - createTestUser, - deleteTestApiKey, - deleteTestApp, - deleteTestTeam, - deleteTestUser, + createTestApiKey, + createTestApp, + createTestTeam, + createTestUser, + deleteTestApiKey, + deleteTestApp, + deleteTestTeam, + deleteTestUser, } from "helpers"; import { createAppSession } from "helpers/auth0"; From 063343648c5f3c46ee68f365b623959b10e7f22d Mon Sep 17 00:00:00 2001 From: mishayouknowme Date: Thu, 11 Sep 2025 17:19:35 +0200 Subject: [PATCH 09/10] Fix --- tests/api/.env.development | 3 +- tests/api/helpers/hasura.ts | 40 +++++- tests/api/jest.config.ts | 2 +- tests/api/jest.setup.ts | 16 +++ .../dev-portal-helpers/create-action.spec.ts | 10 +- .../dev-portal-helpers/create-team.spec.ts | 128 +++++++++--------- .../dev-portal-helpers/graphql-proxy.spec.ts | 5 +- 7 files changed, 128 insertions(+), 76 deletions(-) create mode 100644 tests/api/jest.setup.ts diff --git a/tests/api/.env.development b/tests/api/.env.development index d2176a918..ab6ba03ed 100644 --- a/tests/api/.env.development +++ b/tests/api/.env.development @@ -20,5 +20,4 @@ AWS_REGION=eu-west-1 AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= -GENERAL_SECRET_KEY= - +NAME_SLUG= diff --git a/tests/api/helpers/hasura.ts b/tests/api/helpers/hasura.ts index c3d973046..dcdeec338 100644 --- a/tests/api/helpers/hasura.ts +++ b/tests/api/helpers/hasura.ts @@ -75,6 +75,14 @@ const DELETE_USER_MUTATION = ` } `; +const FIND_USER_BY_AUTH0_QUERY = ` + query FindUserByAuth0Id($auth0Id: String!) { + user(where: {auth0Id: {_eq: $auth0Id}}) { + id + } + } +`; + // Simple GraphQL mutation for creating app_metadata const CREATE_APP_METADATA_MUTATION = ` mutation CreateAppMetadata($object: app_metadata_insert_input!) { @@ -235,13 +243,13 @@ export const createTestTeam = async (name: string) => { }; // Helper for creating test user -export const createTestUser = async (email: string, teamId: string) => { +export const createTestUser = async (email: string, teamId?: string) => { try { const response = (await adminGraphqlClient.request(CREATE_USER_MUTATION, { object: { email, auth0Id: `auth0|test_${Date.now()}`, - team_id: teamId, + ...(teamId && { team_id: teamId }), ironclad_id: `ironclad_test_${Date.now()}`, world_id_nullifier: `0x${Date.now().toString(16)}`, }, @@ -285,6 +293,26 @@ export const deleteTestTeam = async (teamId: string) => { } }; +// Helper for finding user by auth0Id +export const findUserByAuth0Id = async (auth0Id: string) => { + try { + const response = (await adminGraphqlClient.request( + FIND_USER_BY_AUTH0_QUERY, + { + auth0Id, + }, + )) as any; + + return response.user?.[0]?.id || null; + } catch (error: any) { + const errorMessage = + error?.response?.data?.message || error?.message || "Unknown error"; + throw new Error( + `Failed to find user by auth0Id ${auth0Id}: ${errorMessage}`, + ); + } +}; + // Helper for deleting test user export const deleteTestUser = async (userId: string) => { try { @@ -460,9 +488,9 @@ export const createTestApiKey = async ( teamId: string, name: string = "Test API Key", ) => { - const GENERAL_SECRET_KEY = process.env.GENERAL_SECRET_KEY; - if (!GENERAL_SECRET_KEY) { - throw new Error("GENERAL_SECRET_KEY env var must be set for tests"); + const AUTH0_SECRET = process.env.AUTH0_SECRET; + if (!AUTH0_SECRET) { + throw new Error("AUTH0_SECRET env var must be set for tests"); } // Step 1: Create API key record to get UUID @@ -485,7 +513,7 @@ export const createTestApiKey = async ( // Step 2: Generate real secret and hash using UUID as key_id const secret = `sk_${require("crypto").randomBytes(24).toString("hex")}`; - const hmac = require("crypto").createHmac("sha256", GENERAL_SECRET_KEY); + const hmac = require("crypto").createHmac("sha256", AUTH0_SECRET); hmac.update(`${uuid_id}.${secret}`); const hashed_secret = hmac.digest("hex"); diff --git a/tests/api/jest.config.ts b/tests/api/jest.config.ts index 00a873572..8cbff830b 100644 --- a/tests/api/jest.config.ts +++ b/tests/api/jest.config.ts @@ -7,7 +7,7 @@ dotenv.config({ path: ".env.development" }); const config: Config = { preset: "ts-jest", - setupFilesAfterEnv: ["jest-expect-message"], + setupFilesAfterEnv: ["jest-expect-message", "/jest.setup.ts"], testMatch: ["/specs/**/*.spec.ts"], testTimeout: 30000, moduleDirectories: ["node_modules", "", "/../../web"], diff --git a/tests/api/jest.setup.ts b/tests/api/jest.setup.ts new file mode 100644 index 000000000..a70ffbd0d --- /dev/null +++ b/tests/api/jest.setup.ts @@ -0,0 +1,16 @@ +// Global setup for API tests - validates required environment variables + +const requiredEnvVars = ["INTERNAL_API_URL", "NAME_SLUG"]; + +beforeAll(() => { + const missingEnvVars = requiredEnvVars.filter( + (envVar) => !process.env[envVar], + ); + + if (missingEnvVars.length > 0) { + throw new Error( + `Required environment variables are not set: ${missingEnvVars.join(", ")}\n` + + "Please check your environment configuration.", + ); + } +}); diff --git a/tests/api/specs/dev-portal-helpers/create-action.spec.ts b/tests/api/specs/dev-portal-helpers/create-action.spec.ts index 5fe482801..f9b3cf251 100644 --- a/tests/api/specs/dev-portal-helpers/create-action.spec.ts +++ b/tests/api/specs/dev-portal-helpers/create-action.spec.ts @@ -4,6 +4,7 @@ import { createTestApp, createTestTeam, createTestUser, + deleteTestAction, deleteTestApiKey, deleteTestApp, deleteTestTeam, @@ -11,6 +12,7 @@ import { } from "helpers"; const INTERNAL_API_URL = process.env.INTERNAL_API_URL; +const NAME_SLUG = process.env.NAME_SLUG; describe("Dev Portal Helpers API Endpoints", () => { describe("POST /api/v2/create-action/[app_id]", () => { @@ -30,7 +32,7 @@ describe("Dev Portal Helpers API Endpoints", () => { const teamId = await createTestTeam("Test Team"); cleanUpFunctions.push(async () => await deleteTestTeam(teamId)); - const userEmail = `testuser_${Date.now()}@example.com`; + const userEmail = `qa+${NAME_SLUG}+${Date.now()}@toolsforhumanity.com`; const userId = await createTestUser(userEmail, teamId); cleanUpFunctions.push(async () => await deleteTestUser(userId)); @@ -77,6 +79,12 @@ describe("Dev Portal Helpers API Endpoints", () => { }), }), ); + + // Extract action_id from response for cleanup + const createdActionId = response.data.action.id; + cleanUpFunctions.push( + async () => await deleteTestAction(createdActionId), + ); }); }); }); diff --git a/tests/api/specs/dev-portal-helpers/create-team.spec.ts b/tests/api/specs/dev-portal-helpers/create-team.spec.ts index db8e6ec0a..4354b7234 100644 --- a/tests/api/specs/dev-portal-helpers/create-team.spec.ts +++ b/tests/api/specs/dev-portal-helpers/create-team.spec.ts @@ -1,13 +1,14 @@ import axios from "axios"; import { - createTestTeam, createTestUser, deleteTestTeam, deleteTestUser, + findUserByAuth0Id, } from "helpers"; import { createAppSession } from "helpers/auth0"; const INTERNAL_API_URL = process.env.INTERNAL_API_URL; +const NAME_SLUG = process.env.NAME_SLUG; describe("Dev Portal Helpers API Endpoints", () => { describe("POST /api/create-team", () => { @@ -22,14 +23,12 @@ describe("Dev Portal Helpers API Endpoints", () => { cleanUpFunctions = []; }); - it("Create Team Successfully For Existing User", async () => { - const userEmail = `existing_${Date.now()}@example.com`; - const testTeamId = await createTestTeam("Initial Team"); - const testUserId = await createTestUser(userEmail, testTeamId); + it("Create team successfully for existing user", async () => { + const userEmail = `qa+${NAME_SLUG}+${Date.now()}@toolsforhumanity.com`; + const testUserId = await createTestUser(userEmail); - // Add cleanup functions in reverse order (users first, then teams due to foreign key constraints) + // Add cleanup functions cleanUpFunctions.push(async () => await deleteTestUser(testUserId)); - cleanUpFunctions.push(async () => await deleteTestTeam(testTeamId)); // Generate unique auth0Id to prevent constraint violations const uniqueAuth0Id = `auth0|test_existing_user_${Date.now()}`; @@ -49,35 +48,35 @@ describe("Dev Portal Helpers API Endpoints", () => { hasUser: true, }; - try { - const response = await axios.post( - `${INTERNAL_API_URL}/api/create-team`, - teamData, - { - headers: { - "Content-Type": "application/json", - Cookie: existingUserSession, - }, + const response = await axios.post( + `${INTERNAL_API_URL}/api/create-team`, + teamData, + { + headers: { + "Content-Type": "application/json", + Cookie: existingUserSession, }, - ); - - expect( - response.status, - `Create team request resolved with a wrong code:\n${JSON.stringify(response.data, null, 2)}`, - ).toBe(200); - - expect(response.data).toEqual( - expect.objectContaining({ - returnTo: expect.stringMatching(/^\/teams\/team_[a-f0-9]{32}$/), - }), - ); - } catch (error: any) { - throw error; - } + }, + ); + + expect( + response.status, + `Create team request resolved with a wrong code:\n${JSON.stringify(response.data, null, 2)}`, + ).toBe(200); + + expect(response.data).toEqual( + expect.objectContaining({ + returnTo: expect.stringMatching(/^\/teams\/team_[a-f0-9]{32}$/), + }), + ); + + // Extract team_id from returnTo URL for cleanup + const createdTeamId = response.data.returnTo.split("/teams/")[1]; + cleanUpFunctions.push(async () => await deleteTestTeam(createdTeamId)); }); - it("Create Team Successfully For New User", async () => { - const userEmail = `newuser_${Date.now()}@example.com`; + it("Create an initial team along with the user", async () => { + const userEmail = `qa+${NAME_SLUG}+${Date.now()}@toolsforhumanity.com`; const teamData = { team_name: "Test Team for New User", hasUser: false, @@ -98,38 +97,39 @@ describe("Dev Portal Helpers API Endpoints", () => { const sessionCookie = await createAppSession(auth0Session); - try { - const response = await axios.post( - `${INTERNAL_API_URL}/api/create-team`, - teamData, - { - headers: { - "Content-Type": "application/json", - Cookie: sessionCookie, - }, + const response = await axios.post( + `${INTERNAL_API_URL}/api/create-team`, + teamData, + { + headers: { + "Content-Type": "application/json", + Cookie: sessionCookie, }, - ); - - expect(response.status).toBe(200); - expect(response.data).toEqual( - expect.objectContaining({ - returnTo: expect.stringMatching( - /^\/teams\/team_[a-f0-9]{32}\/apps\/$/, - ), - }), - ); - } catch (error: any) { - // Skip test if Ironclad API is not configured (expected in test env) - if (error.response?.status === 500) { - const detail = error.response?.data?.detail || ""; - if ( - detail.includes("Failed to send acceptance") || - detail.includes("Failed to create team") - ) { - return; // Skip test - external API not available - } - } - throw error; + }, + ); + + expect( + response.status, + `Create team request resolved with a wrong code:\n${JSON.stringify(response.data, null, 2)}`, + ).toBe(200); + expect(response.data).toEqual( + expect.objectContaining({ + returnTo: expect.stringMatching( + /^\/teams\/team_[a-f0-9]{32}\/apps\/$/, + ), + }), + ); + + // Extract team_id from returnTo URL for cleanup + const createdTeamId = response.data.returnTo + .split("/teams/")[1] + .split("/apps/")[0]; + cleanUpFunctions.push(async () => await deleteTestTeam(createdTeamId)); + + // Find and cleanup the created user by auth0Id + const createdUserId = await findUserByAuth0Id(uniqueAuth0Id); + if (createdUserId) { + cleanUpFunctions.push(async () => await deleteTestUser(createdUserId)); } }); }); diff --git a/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts b/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts index 632c1fb0d..4e0f0952e 100644 --- a/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts +++ b/tests/api/specs/dev-portal-helpers/graphql-proxy.spec.ts @@ -13,6 +13,7 @@ import { createAppSession } from "helpers/auth0"; const INTERNAL_API_URL = process.env.INTERNAL_API_URL; const INTERNAL_ENDPOINTS_SECRET = process.env.INTERNAL_ENDPOINTS_SECRET; +const NAME_SLUG = process.env.NAME_SLUG; describe("Dev Portal Helpers API Endpoints", () => { describe("POST /api/v1/graphql", () => { @@ -33,7 +34,7 @@ describe("Dev Portal Helpers API Endpoints", () => { const teamId = await createTestTeam("Test Team GraphQL"); cleanUpFunctions.push(async () => await deleteTestTeam(teamId)); - const userEmail = `testuser_${Date.now()}@example.com`; + const userEmail = `qa+${NAME_SLUG}+${Date.now()}@toolsforhumanity.com`; const userId = await createTestUser(userEmail, teamId); cleanUpFunctions.push(async () => await deleteTestUser(userId)); @@ -83,7 +84,7 @@ describe("Dev Portal Helpers API Endpoints", () => { const teamId = await createTestTeam("Test Team Auth0 GraphQL"); cleanUpFunctions.push(async () => await deleteTestTeam(teamId)); - const userEmail = `auth0user_${Date.now()}@example.com`; + const userEmail = `qa+${NAME_SLUG}+${Date.now()}@toolsforhumanity.com`; const userId = await createTestUser(userEmail, teamId); cleanUpFunctions.push(async () => await deleteTestUser(userId)); From 16385f3293580703b97657c71278ddce6c28fbb2 Mon Sep 17 00:00:00 2001 From: mishayouknowme Date: Thu, 11 Sep 2025 17:34:52 +0200 Subject: [PATCH 10/10] Update id generation --- tests/api/helpers/hasura.ts | 43 +++++++++++++++---------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/tests/api/helpers/hasura.ts b/tests/api/helpers/hasura.ts index dcdeec338..4dc6fc4a2 100644 --- a/tests/api/helpers/hasura.ts +++ b/tests/api/helpers/hasura.ts @@ -1,3 +1,4 @@ +import crypto from "crypto"; import { GraphQLClient } from "graphql-request"; // GraphQL client with admin privileges for creating test data @@ -493,51 +494,41 @@ export const createTestApiKey = async ( throw new Error("AUTH0_SECRET env var must be set for tests"); } - // Step 1: Create API key record to get UUID + // Step 1: Generate friendly ID in Hasura format (key_xxxxxxxxxx) + const randomId = crypto.randomBytes(8).toString("hex"); + const keyId = `key_${randomId}`; + + // Step 2: Generate secret and hash using the pre-generated ID + const secret = `sk_${crypto.randomBytes(24).toString("hex")}`; + const hmac = crypto.createHmac("sha256", AUTH0_SECRET); + hmac.update(`${keyId}.${secret}`); + const hashed_secret = hmac.digest("hex"); + + // Step 3: Create API key record with the generated ID and hash in one operation const createResponse = (await adminGraphqlClient.request( CREATE_API_KEY_MUTATION, { object: { + id: keyId, // Use our pre-generated ID team_id: teamId, - api_key: "temp_hash", // Temporary hash, will be updated + api_key: hashed_secret, name, is_active: true, }, }, )) as any; - const uuid_id = createResponse.insert_api_key_one?.id; - if (!uuid_id) { + if (!createResponse.insert_api_key_one?.id) { throw new Error("Failed to create API key record"); } - // Step 2: Generate real secret and hash using UUID as key_id - const secret = `sk_${require("crypto").randomBytes(24).toString("hex")}`; - const hmac = require("crypto").createHmac("sha256", AUTH0_SECRET); - hmac.update(`${uuid_id}.${secret}`); - const hashed_secret = hmac.digest("hex"); - - // Step 3: Update with real hash - const updateMutation = ` - mutation UpdateApiKey($id: String!, $api_key: String!) { - update_api_key_by_pk(pk_columns: {id: $id}, _set: {api_key: $api_key}) { - id - } - } - `; - - await adminGraphqlClient.request(updateMutation, { - id: uuid_id, - api_key: hashed_secret, - }); - // Step 4: Create proper API key header format for HTTP auth - const credentials = `${uuid_id}:${secret}`; + const credentials = `${keyId}:${secret}`; const encodedCredentials = Buffer.from(credentials).toString("base64"); const apiKeyHeader = `api_${encodedCredentials}`; return { - apiKeyId: uuid_id, + apiKeyId: keyId, secret, apiKeyHeader, };