diff --git a/packages/cre-sdk/src/sdk/utils/capabilities/http/http-helpers.test.ts b/packages/cre-sdk/src/sdk/utils/capabilities/http/http-helpers.test.ts index 3ade902..9e8e431 100644 --- a/packages/cre-sdk/src/sdk/utils/capabilities/http/http-helpers.test.ts +++ b/packages/cre-sdk/src/sdk/utils/capabilities/http/http-helpers.test.ts @@ -1,5 +1,20 @@ -import { describe, expect, it } from 'bun:test' -import type { Response } from '@cre/generated/capabilities/networking/http/v1alpha/client_pb' +import { describe, expect, it, mock } from 'bun:test' +import { create } from '@bufbuild/protobuf' +import type { + RequestJson, + Response, +} from '@cre/generated/capabilities/networking/http/v1alpha/client_pb' +import { + AttributedSignatureSchema, + type ReportResponse, + ReportResponseSchema, +} from '@cre/generated/sdk/v1alpha/sdk_pb' +import { + ClientCapability, + SendRequester, +} from '@cre/generated-sdk/capabilities/networking/http/v1alpha/client_sdk_gen' +import type { NodeRuntime } from '@cre/index' +import { Report } from '@cre/sdk' import { getHeader, json, ok, text } from './http-helpers' // Mock Response object for testing @@ -105,3 +120,63 @@ describe('HTTP Helpers', () => { }) }) }) + +describe('sendReport extension', () => { + const anyRequest: RequestJson = { + body: 'test', + headers: { a: 'b' }, + method: 'GET', + url: 'https://example.com', + } + + const anyResponse = createMockResponse(200, new Uint8Array(), { a: 'b' }) + + const anyReportResponse = create(ReportResponseSchema, { + configDigest: new Uint8Array([1, 2, 3]), + seqNr: 101n, + reportContext: new Uint8Array([4, 5, 6]), + rawReport: new Uint8Array([7, 8, 9]), + sigs: [ + create(AttributedSignatureSchema, { + signature: new Uint8Array([10, 11, 12]), + signerId: 13, + }), + create(AttributedSignatureSchema, { + signature: new Uint8Array([16, 17, 18]), + signerId: 14, + }), + ], + }) + const anyReport = new Report(anyReportResponse) + + // safe for this test as we don't use the runtime itself + const anyRuntime = {} as NodeRuntime + + const produceReport = (reportResponse: ReportResponse) => { + expect(reportResponse).toEqual(anyReportResponse) + return anyRequest + } + + class ClientCapabilityMock extends ClientCapability { + // biome-ignore lint/suspicious/noExplicitAny: Test mock needs to override complex overloaded method signatures. Using 'any' bypasses TypeScript's strict overload compatibility checks that would otherwise require redeclaring all overloaded signatures exactly. + sendRequest(...args: any[]): any { + expect(2).toEqual(args.length) + const [runtime, input] = args + expect(runtime).toBe(anyRuntime) + expect(input).toBe(anyRequest) + return { result: () => anyResponse } + } + } + + it('ClientCapability should call the transform function and use the result', () => { + const clientCapability = new ClientCapabilityMock() + const response = clientCapability.sendReport(anyRuntime, anyReport, produceReport) + expect(response.result()).toBe(anyResponse) + }) + + it('SendRequester should call the transform function and use the result', () => { + const sendRequester = new SendRequester(anyRuntime, new ClientCapabilityMock()) + const response = sendRequester.sendReport(anyReport, produceReport) + expect(response.result()).toBe(anyResponse) + }) +}) diff --git a/packages/cre-sdk/src/sdk/utils/capabilities/http/http-helpers.ts b/packages/cre-sdk/src/sdk/utils/capabilities/http/http-helpers.ts index 49df6e1..9f34f41 100644 --- a/packages/cre-sdk/src/sdk/utils/capabilities/http/http-helpers.ts +++ b/packages/cre-sdk/src/sdk/utils/capabilities/http/http-helpers.ts @@ -1,4 +1,15 @@ -import type { Response } from '@cre/generated/capabilities/networking/http/v1alpha/client_pb' +import type { + Request, + RequestJson, + Response, +} from '@cre/generated/capabilities/networking/http/v1alpha/client_pb' +import type { ReportResponse } from '@cre/generated/sdk/v1alpha/sdk_pb' +import type { + ClientCapability, + SendRequester, +} from '@cre/generated-sdk/capabilities/networking/http/v1alpha/client_sdk_gen' +import type { NodeRuntime } from '@cre/sdk' +import type { Report } from '@cre/sdk/report' /** * HTTP Response Helper Functions @@ -159,3 +170,108 @@ export function ok( return responseOrFn.statusCode >= 200 && responseOrFn.statusCode < 300 } } + +// ============================================================================ +// SendReport Helper Methods for ClientCapability and SendRequester +// ============================================================================ + +/** + * SendReport functions the same as SendRequest, but takes a Report and a function + * to convert the inner ReportResponse to a Request. + * Note that caching is limited as reports may contain different sets of signatures + * on different nodes, leading to a cache miss. + * + * @param runtime - The runtime instance + * @param report - The Report to process + * @param fn - Function to convert ReportResponse to Request + * @returns Response result function + */ +function sendReport( + this: ClientCapability, + runtime: NodeRuntime, + report: Report, + fn: (reportResponse: ReportResponse) => Request | RequestJson, +): { result: () => Response } { + const rawReport = report.x_generatedCodeOnly_unwrap() + const request = fn(rawReport) + return this.sendRequest(runtime, request) +} + +/** + * SendReport functions the same as SendRequest, but takes a Report and a function + * to convert the inner ReportResponse to a Request. + * Note that caching is limited as reports may contain different sets of signatures + * on different nodes, leading to a cache miss. + * + * @param report - The Report to process + * @param fn - Function to convert ReportResponse to Request + * @returns Response result function + */ +function sendRequesterSendReport( + this: SendRequester, + report: Report, + fn: (reportResponse: ReportResponse) => Request | RequestJson, +): { result: () => Response } { + const rawReport = report.x_generatedCodeOnly_unwrap() + const request = fn(rawReport) + return this.sendRequest(request) +} + +// ============================================================================ +// Prototype Extensions +// ============================================================================ + +// Import the actual classes for prototype extension +import { + ClientCapability as ClientCapabilityClass, + SendRequester as SendRequesterClass, +} from '@cre/generated-sdk/capabilities/networking/http/v1alpha/client_sdk_gen' + +// Extend ClientCapability prototype +ClientCapabilityClass.prototype.sendReport = sendReport + +// Extend SendRequester prototype +SendRequesterClass.prototype.sendReport = sendRequesterSendReport + +// ============================================================================ +// Type Declarations for Prototype Extensions +// ============================================================================ + +// Augment the module declarations to include the new methods +declare module '@cre/generated-sdk/capabilities/networking/http/v1alpha/client_sdk_gen' { + interface ClientCapability { + /** + * SendReport functions the same as SendRequest, but takes a Report and a function + * to convert the inner ReportResponse to a Request. + * Note that caching is limited as reports may contain different sets of signatures + * on different nodes, leading to a cache miss. + * + * @param runtime - The runtime instance + * @param report - The Report to process + * @param fn - Function to convert ReportResponse to Request + * @returns Response result function + */ + sendReport( + runtime: NodeRuntime, + report: Report, + fn: (reportResponse: ReportResponse) => Request | RequestJson, + ): { result: () => Response } + } + + interface SendRequester { + /** + * SendReport functions the same as SendRequest, but takes a Report and a function + * to convert the inner ReportResponse to a Request. + * Note that caching is limited as reports may contain different sets of signatures + * on different nodes, leading to a cache miss. + * + * @param report - The Report to process + * @param fn - Function to convert ReportResponse to Request + * @returns Response result function + */ + sendReport( + report: Report, + fn: (reportResponse: ReportResponse) => Request | RequestJson, + ): { result: () => Response } + } +}