From 98c24780d43e866155a976d25d9937a6ba4f4dbb Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Thu, 13 Nov 2025 19:26:30 +0530 Subject: [PATCH 1/3] chore: fix the issue with evault core sending back the same w3id --- .../src/core/protocol/graphql-server.spec.ts | 243 ++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 infrastructure/evault-core/src/core/protocol/graphql-server.spec.ts diff --git a/infrastructure/evault-core/src/core/protocol/graphql-server.spec.ts b/infrastructure/evault-core/src/core/protocol/graphql-server.spec.ts new file mode 100644 index 00000000..bcadb3b0 --- /dev/null +++ b/infrastructure/evault-core/src/core/protocol/graphql-server.spec.ts @@ -0,0 +1,243 @@ +import { describe, it, expect, beforeAll, afterAll, beforeEach, vi } from "vitest"; +import axios from "axios"; +import { + setupE2ETestServer, + teardownE2ETestServer, + provisionTestEVault, + makeGraphQLRequest, + type E2ETestServer, + type ProvisionedEVault, +} from "../../test-utils/e2e-setup"; + +describe("GraphQLServer Webhook Payload W3ID", () => { + let server: E2ETestServer; + let evault1: ProvisionedEVault; + let evault2: ProvisionedEVault; + const evaultW3ID = "evault-w3id-123"; + let realAxios: typeof axios; + + beforeAll(async () => { + // Get real axios before any spying + realAxios = (await vi.importActual("axios")) as typeof axios; + + server = await setupE2ETestServer(); + evault1 = await provisionTestEVault(server); + evault2 = await provisionTestEVault(server); + }, 120000); + + afterAll(async () => { + await teardownE2ETestServer(server); + }); + + beforeEach(() => { + vi.clearAllMocks(); + + // Mock axios.get for platforms endpoint only + vi.spyOn(axios, "get").mockImplementation((url: string | any) => { + if (typeof url === "string" && url.includes("/platforms")) { + return Promise.resolve({ + data: ["http://localhost:9999"], // Mock platform URL + }) as any; + } + // For other GET requests, call through to real axios + return realAxios.get(url); + }); + + // Spy on axios.post to capture webhook payloads + // For webhook calls, return success. For others, call through to real axios + vi.spyOn(axios, "post").mockImplementation((url: string | any, data?: any, config?: any) => { + // If it's a webhook call, capture it and return success + if (typeof url === "string" && url.includes("/api/webhook")) { + return Promise.resolve({ status: 200, data: {} }) as any; + } + // For GraphQL and other requests, call through to real axios + return realAxios.post(url, data, config); + }); + }); + + describe("storeMetaEnvelope webhook payload", () => { + it("should include user's W3ID (eName) in webhook payload, not eVault's W3ID", async () => { + const testData = { field: "value", test: "store-test" }; + const testOntology = "WebhookTestOntology"; + + // Make GraphQL mutation with user's W3ID in X-ENAME header + const mutation = ` + mutation StoreMetaEnvelope($input: StoreMetaEnvelopeInput!) { + storeMetaEnvelope(input: $input) { + id + ontology + } + } + `; + + await makeGraphQLRequest(server, mutation, { + input: { + ontology: testOntology, + payload: testData, + acl: ["*"], + }, + }, { + "X-ENAME": evault1.w3id, + }); + + // Wait for the setTimeout delay (3 seconds) in the actual code + await new Promise(resolve => setTimeout(resolve, 3500)); + + // Verify axios.post was called (webhook delivery) + expect(axios.post).toHaveBeenCalled(); + + // Get the webhook payload from the axios.post call + const webhookCalls = (axios.post as any).mock.calls; + const webhookCall = webhookCalls.find((call: any[]) => + typeof call[0] === "string" && call[0].includes("/api/webhook") + ); + + expect(webhookCall).toBeDefined(); + const webhookPayload = webhookCall[1]; // Second argument is the payload + + // Verify the webhook payload contains the user's W3ID, not the eVault's W3ID + expect(webhookPayload).toBeDefined(); + expect(webhookPayload.w3id).toBe(evault1.w3id); + expect(webhookPayload.w3id).not.toBe(evaultW3ID); + expect(webhookPayload.data).toEqual(testData); + expect(webhookPayload.schemaId).toBe(testOntology); + }); + + it("should use different W3IDs for different users in webhook payloads", async () => { + const testData1 = { user: "1", data: "test1" }; + const testData2 = { user: "2", data: "test2" }; + const testOntology = "MultiUserWebhookTest"; + + const mutation = ` + mutation StoreMetaEnvelope($input: StoreMetaEnvelopeInput!) { + storeMetaEnvelope(input: $input) { + id + ontology + } + } + `; + + // Store for user1 + await makeGraphQLRequest(server, mutation, { + input: { + ontology: testOntology, + payload: testData1, + acl: ["*"], + }, + }, { + "X-ENAME": evault1.w3id, + }); + + // Store for user2 + await makeGraphQLRequest(server, mutation, { + input: { + ontology: testOntology, + payload: testData2, + acl: ["*"], + }, + }, { + "X-ENAME": evault2.w3id, + }); + + // Wait for setTimeout delays + await new Promise(resolve => setTimeout(resolve, 3500)); + + // Get all webhook calls + const webhookCalls = (axios.post as any).mock.calls.filter((call: any[]) => + typeof call[0] === "string" && call[0].includes("/api/webhook") + ); + + expect(webhookCalls.length).toBeGreaterThanOrEqual(2); + + // Find payloads by their data + const payload1 = webhookCalls.find((call: any[]) => + call[1]?.data?.user === "1" + )?.[1]; + const payload2 = webhookCalls.find((call: any[]) => + call[1]?.data?.user === "2" + )?.[1]; + + expect(payload1).toBeDefined(); + expect(payload1.w3id).toBe(evault1.w3id); + expect(payload2).toBeDefined(); + expect(payload2.w3id).toBe(evault2.w3id); + expect(payload1.w3id).not.toBe(payload2.w3id); + }); + }); + + describe("updateMetaEnvelopeById webhook payload", () => { + it("should include user's W3ID (eName) in webhook payload, not eVault's W3ID", async () => { + const testData = { field: "updated-value", test: "update-test" }; + const testOntology = "UpdateWebhookTestOntology"; + + // First, create an envelope + const createMutation = ` + mutation StoreMetaEnvelope($input: StoreMetaEnvelopeInput!) { + storeMetaEnvelope(input: $input) { + id + ontology + } + } + `; + + const createResult = await makeGraphQLRequest(server, createMutation, { + input: { + ontology: testOntology, + payload: { field: "initial-value" }, + acl: ["*"], + }, + }, { + "X-ENAME": evault1.w3id, + }); + + const envelopeId = createResult.storeMetaEnvelope.id; + + // Clear previous webhook calls + (axios.post as any).mockClear(); + + // Now update the envelope + const updateMutation = ` + mutation UpdateMetaEnvelopeById($id: String!, $input: UpdateMetaEnvelopeInput!) { + updateMetaEnvelopeById(id: $id, input: $input) { + id + ontology + } + } + `; + + await makeGraphQLRequest(server, updateMutation, { + id: envelopeId, + input: { + ontology: testOntology, + payload: testData, + acl: ["*"], + }, + }, { + "X-ENAME": evault1.w3id, + }); + + // Wait a bit for webhook delivery (update doesn't have setTimeout delay) + await new Promise(resolve => setTimeout(resolve, 500)); + + // Verify axios.post was called (webhook delivery) + expect(axios.post).toHaveBeenCalled(); + + // Get the webhook payload + const webhookCalls = (axios.post as any).mock.calls.filter((call: any[]) => + typeof call[0] === "string" && call[0].includes("/api/webhook") + ); + + expect(webhookCalls.length).toBeGreaterThan(0); + const webhookPayload = webhookCalls[0][1]; + + // Verify the webhook payload contains the user's W3ID, not the eVault's W3ID + expect(webhookPayload).toBeDefined(); + expect(webhookPayload.w3id).toBe(evault1.w3id); + expect(webhookPayload.w3id).not.toBe(evaultW3ID); + expect(webhookPayload.id).toBe(envelopeId); + expect(webhookPayload.data).toEqual(testData); + expect(webhookPayload.schemaId).toBe(testOntology); + }); + }); +}); + From 2760d5328fc05a0e7bc03e8bbd6d1f998484a5c8 Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Thu, 13 Nov 2025 19:37:47 +0530 Subject: [PATCH 2/3] tests: add unit tests for evault eName idempotency --- .../src/core/protocol/graphql-server.spec.ts | 80 +++++++++++++------ .../src/core/protocol/graphql-server.ts | 4 +- 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/infrastructure/evault-core/src/core/protocol/graphql-server.spec.ts b/infrastructure/evault-core/src/core/protocol/graphql-server.spec.ts index bcadb3b0..52d707d7 100644 --- a/infrastructure/evault-core/src/core/protocol/graphql-server.spec.ts +++ b/infrastructure/evault-core/src/core/protocol/graphql-server.spec.ts @@ -9,17 +9,19 @@ import { type ProvisionedEVault, } from "../../test-utils/e2e-setup"; +// Store original axios functions before any spying happens +const originalAxiosGet = axios.get; +const originalAxiosPost = axios.post; + describe("GraphQLServer Webhook Payload W3ID", () => { let server: E2ETestServer; let evault1: ProvisionedEVault; let evault2: ProvisionedEVault; const evaultW3ID = "evault-w3id-123"; - let realAxios: typeof axios; + let axiosGetSpy: any; + let axiosPostSpy: any; beforeAll(async () => { - // Get real axios before any spying - realAxios = (await vi.importActual("axios")) as typeof axios; - server = await setupE2ETestServer(); evault1 = await provisionTestEVault(server); evault2 = await provisionTestEVault(server); @@ -27,45 +29,64 @@ describe("GraphQLServer Webhook Payload W3ID", () => { afterAll(async () => { await teardownE2ETestServer(server); + // Restore original implementations + if (axiosGetSpy) { + axiosGetSpy.mockRestore(); + } + if (axiosPostSpy) { + axiosPostSpy.mockRestore(); + } }); beforeEach(() => { + // Restore any existing spies first + if (axiosGetSpy) { + axiosGetSpy.mockRestore(); + } + if (axiosPostSpy) { + axiosPostSpy.mockRestore(); + } + vi.clearAllMocks(); // Mock axios.get for platforms endpoint only - vi.spyOn(axios, "get").mockImplementation((url: string | any) => { + axiosGetSpy = vi.spyOn(axios, "get").mockImplementation((url: string | any) => { if (typeof url === "string" && url.includes("/platforms")) { return Promise.resolve({ data: ["http://localhost:9999"], // Mock platform URL }) as any; } - // For other GET requests, call through to real axios - return realAxios.get(url); + // For other GET requests, call through to original (stored before spying) + return originalAxiosGet.call(axios, url); }); // Spy on axios.post to capture webhook payloads - // For webhook calls, return success. For others, call through to real axios - vi.spyOn(axios, "post").mockImplementation((url: string | any, data?: any, config?: any) => { + axiosPostSpy = vi.spyOn(axios, "post").mockImplementation((url: string | any, data?: any, config?: any) => { // If it's a webhook call, capture it and return success + // Note: axios.post(url, data, config) - data is the second parameter if (typeof url === "string" && url.includes("/api/webhook")) { + // Log for debugging + console.log("Webhook intercepted:", { url, data }); return Promise.resolve({ status: 200, data: {} }) as any; } - // For GraphQL and other requests, call through to real axios - return realAxios.post(url, data, config); + // For GraphQL and other requests, call through to original (stored before spying) + return originalAxiosPost.call(axios, url, data, config); }); }); describe("storeMetaEnvelope webhook payload", () => { - it("should include user's W3ID (eName) in webhook payload, not eVault's W3ID", async () => { + it("should include X-ENAME in webhook payload", async () => { const testData = { field: "value", test: "store-test" }; const testOntology = "WebhookTestOntology"; // Make GraphQL mutation with user's W3ID in X-ENAME header const mutation = ` - mutation StoreMetaEnvelope($input: StoreMetaEnvelopeInput!) { + mutation StoreMetaEnvelope($input: MetaEnvelopeInput!) { storeMetaEnvelope(input: $input) { - id - ontology + metaEnvelope { + id + ontology + } } } `; @@ -95,6 +116,9 @@ describe("GraphQLServer Webhook Payload W3ID", () => { expect(webhookCall).toBeDefined(); const webhookPayload = webhookCall[1]; // Second argument is the payload + console.log("Webhook payload:", JSON.stringify(webhookPayload, null, 2)); + console.log("Expected w3id:", evault1.w3id); + // Verify the webhook payload contains the user's W3ID, not the eVault's W3ID expect(webhookPayload).toBeDefined(); expect(webhookPayload.w3id).toBe(evault1.w3id); @@ -109,10 +133,12 @@ describe("GraphQLServer Webhook Payload W3ID", () => { const testOntology = "MultiUserWebhookTest"; const mutation = ` - mutation StoreMetaEnvelope($input: StoreMetaEnvelopeInput!) { + mutation StoreMetaEnvelope($input: MetaEnvelopeInput!) { storeMetaEnvelope(input: $input) { - id - ontology + metaEnvelope { + id + ontology + } } } `; @@ -172,10 +198,12 @@ describe("GraphQLServer Webhook Payload W3ID", () => { // First, create an envelope const createMutation = ` - mutation StoreMetaEnvelope($input: StoreMetaEnvelopeInput!) { + mutation StoreMetaEnvelope($input: MetaEnvelopeInput!) { storeMetaEnvelope(input: $input) { - id - ontology + metaEnvelope { + id + ontology + } } } `; @@ -190,17 +218,19 @@ describe("GraphQLServer Webhook Payload W3ID", () => { "X-ENAME": evault1.w3id, }); - const envelopeId = createResult.storeMetaEnvelope.id; + const envelopeId = createResult.storeMetaEnvelope.metaEnvelope.id; // Clear previous webhook calls (axios.post as any).mockClear(); // Now update the envelope const updateMutation = ` - mutation UpdateMetaEnvelopeById($id: String!, $input: UpdateMetaEnvelopeInput!) { + mutation UpdateMetaEnvelopeById($id: String!, $input: MetaEnvelopeInput!) { updateMetaEnvelopeById(id: $id, input: $input) { - id - ontology + metaEnvelope { + id + ontology + } } } `; diff --git a/infrastructure/evault-core/src/core/protocol/graphql-server.ts b/infrastructure/evault-core/src/core/protocol/graphql-server.ts index 3419d52c..d47893a3 100644 --- a/infrastructure/evault-core/src/core/protocol/graphql-server.ts +++ b/infrastructure/evault-core/src/core/protocol/graphql-server.ts @@ -207,7 +207,7 @@ export class GraphQLServer { context.tokenPayload?.platform || null; const webhookPayload = { id: result.metaEnvelope.id, - w3id: this.getCurrentEvaultW3ID(), + w3id: context.eName, evaultPublicKey: this.evaultPublicKey, data: input.payload, schemaId: input.ontology, @@ -272,7 +272,7 @@ export class GraphQLServer { context.tokenPayload?.platform || null; const webhookPayload = { id: id, - w3id: this.getCurrentEvaultW3ID(), + w3id: context.eName, evaultPublicKey: this.evaultPublicKey, data: input.payload, schemaId: input.ontology, From 4ec7f62414eca96a6c4e2a335f477ecba12ca0ec Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Thu, 13 Nov 2025 19:44:28 +0530 Subject: [PATCH 3/3] fix: axios args thing --- .../evault-core/src/core/protocol/graphql-server.spec.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/infrastructure/evault-core/src/core/protocol/graphql-server.spec.ts b/infrastructure/evault-core/src/core/protocol/graphql-server.spec.ts index 52d707d7..b4287a88 100644 --- a/infrastructure/evault-core/src/core/protocol/graphql-server.spec.ts +++ b/infrastructure/evault-core/src/core/protocol/graphql-server.spec.ts @@ -50,14 +50,15 @@ describe("GraphQLServer Webhook Payload W3ID", () => { vi.clearAllMocks(); // Mock axios.get for platforms endpoint only - axiosGetSpy = vi.spyOn(axios, "get").mockImplementation((url: string | any) => { + axiosGetSpy = vi.spyOn(axios, "get").mockImplementation((...args: any[]) => { + const url = args[0]; if (typeof url === "string" && url.includes("/platforms")) { return Promise.resolve({ data: ["http://localhost:9999"], // Mock platform URL }) as any; } - // For other GET requests, call through to original (stored before spying) - return originalAxiosGet.call(axios, url); + // For other GET requests, call through to original with all arguments preserved + return (originalAxiosGet as any).apply(axios, args); }); // Spy on axios.post to capture webhook payloads