diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbd361f3..2387b416 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,6 +62,8 @@ jobs: - name: Bootstrap run: ./scripts/bootstrap - - name: Run tests - run: ./scripts/test + - name: Build + run: ./scripts/build-all + - name: Run tests + run: ./scripts/test-all diff --git a/jest.config.ts b/jest.config.ts index 00231b16..ded740d2 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -16,6 +16,7 @@ const config: JestConfigWithTsJest = { '/dist/', '/deno/', '/deno_tests/', + '/packages/', ], testPathIgnorePatterns: ['scripts'], }; diff --git a/package.json b/package.json index 62c71965..e24d24cb 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ ], "private": false, "scripts": { - "test": "./scripts/test", + "test": "./scripts/test-all", "build": "./scripts/build-all", "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", "format": "prettier --write --cache --cache-strategy metadata . !dist", diff --git a/packages/bedrock-sdk/jest.config.ts b/packages/bedrock-sdk/jest.config.ts new file mode 100644 index 00000000..d9157e8e --- /dev/null +++ b/packages/bedrock-sdk/jest.config.ts @@ -0,0 +1,17 @@ +import type { JestConfigWithTsJest } from 'ts-jest'; + +const config: JestConfigWithTsJest = { + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', + transform: { + '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }], + }, + moduleNameMapper: { + '^@anthropic-ai/bedrock-sdk$': '/src/index.ts', + '^@anthropic-ai/bedrock-sdk/(.*)$': '/src/$1', + }, + modulePathIgnorePatterns: ['/dist/', '/deno/', '/deno_tests/'], + testPathIgnorePatterns: ['scripts'], +}; + +export default config; diff --git a/packages/bedrock-sdk/package.json b/packages/bedrock-sdk/package.json index 3d6a1c73..ee74d49f 100644 --- a/packages/bedrock-sdk/package.json +++ b/packages/bedrock-sdk/package.json @@ -11,7 +11,7 @@ "packageManager": "yarn@1.22.21", "private": false, "scripts": { - "test": "echo 'no tests defined yet' && exit 1", + "test": "jest", "build": "bash ./build", "prepack": "echo 'to pack, run yarn build && (cd dist; yarn pack)' && exit 1", "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", diff --git a/packages/bedrock-sdk/scripts/test b/packages/bedrock-sdk/scripts/test new file mode 100755 index 00000000..73c44144 --- /dev/null +++ b/packages/bedrock-sdk/scripts/test @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +./node_modules/.bin/jest "$@" diff --git a/packages/bedrock-sdk/src/client.ts b/packages/bedrock-sdk/src/client.ts index cf2f7cac..523df8ba 100644 --- a/packages/bedrock-sdk/src/client.ts +++ b/packages/bedrock-sdk/src/client.ts @@ -117,6 +117,12 @@ export class AnthropicBedrock extends Core.APIClient { } { options.__streamClass = Stream; + if (Core.isObj(options.body)) { + // create a shallow copy of the request body so that code that mutates it later + // doesn't mutate the original user-provided object + options.body = { ...options.body }; + } + if (Core.isObj(options.body)) { if (!options.body['anthropic_version']) { options.body['anthropic_version'] = DEFAULT_VERSION; diff --git a/packages/bedrock-sdk/tests/client.test.ts b/packages/bedrock-sdk/tests/client.test.ts new file mode 100644 index 00000000..d9c42bf2 --- /dev/null +++ b/packages/bedrock-sdk/tests/client.test.ts @@ -0,0 +1,29 @@ +import { AnthropicBedrock } from '@anthropic-ai/bedrock-sdk'; +import Anthropic from '@anthropic-ai/sdk'; + +test('body params are not mutated', async () => { + const client = new AnthropicBedrock({ + baseURL: 'http://localhost:5000/', + awsRegion: 'us-east-1', + awsAccessKey: 'placeholder', + awsSecretKey: 'placeholder', + fetch: (url) => { + return Promise.resolve( + new Response(JSON.stringify({ url, custom: true }), { + headers: { 'Content-Type': 'application/json' }, + }), + ); + }, + }); + + const params: Anthropic.MessageCreateParamsNonStreaming = { + model: 'claude-3-opus-latest', + messages: [], + max_tokens: 100, + }; + const original = JSON.stringify(params); + + await client.messages.create(params); + + expect(JSON.stringify(params)).toEqual(original); +}); diff --git a/packages/vertex-sdk/jest.config.ts b/packages/vertex-sdk/jest.config.ts new file mode 100644 index 00000000..e3f740e4 --- /dev/null +++ b/packages/vertex-sdk/jest.config.ts @@ -0,0 +1,17 @@ +import type { JestConfigWithTsJest } from 'ts-jest'; + +const config: JestConfigWithTsJest = { + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', + transform: { + '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }], + }, + moduleNameMapper: { + '^@anthropic-ai/vertex-sdk$': '/src/index.ts', + '^@anthropic-ai/vertex-sdk/(.*)$': '/src/$1', + }, + modulePathIgnorePatterns: ['/dist/', '/deno/', '/deno_tests/'], + testPathIgnorePatterns: ['scripts'], +}; + +export default config; diff --git a/packages/vertex-sdk/package.json b/packages/vertex-sdk/package.json index ed7c7f84..288a81a8 100644 --- a/packages/vertex-sdk/package.json +++ b/packages/vertex-sdk/package.json @@ -11,7 +11,7 @@ "packageManager": "yarn@1.22.21", "private": false, "scripts": { - "test": "echo 'no tests defined yet' && exit 1", + "test": "jest", "build": "bash ./build", "prepack": "echo 'to pack, run yarn build && (cd dist; yarn pack)' && exit 1", "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", diff --git a/packages/vertex-sdk/scripts/test b/packages/vertex-sdk/scripts/test new file mode 100755 index 00000000..73c44144 --- /dev/null +++ b/packages/vertex-sdk/scripts/test @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +./node_modules/.bin/jest "$@" diff --git a/packages/vertex-sdk/src/client.ts b/packages/vertex-sdk/src/client.ts index 61f67582..06231649 100644 --- a/packages/vertex-sdk/src/client.ts +++ b/packages/vertex-sdk/src/client.ts @@ -114,6 +114,12 @@ export class AnthropicVertex extends Core.APIClient { url: string; timeout: number; } { + if (Core.isObj(options.body)) { + // create a shallow copy of the request body so that code that mutates it later + // doesn't mutate the original user-provided object + options.body = { ...options.body }; + } + if (Core.isObj(options.body)) { if (!options.body['anthropic_version']) { options.body['anthropic_version'] = DEFAULT_VERSION; diff --git a/packages/vertex-sdk/tests/client.test.ts b/packages/vertex-sdk/tests/client.test.ts new file mode 100644 index 00000000..1f9a500a --- /dev/null +++ b/packages/vertex-sdk/tests/client.test.ts @@ -0,0 +1,29 @@ +import { AnthropicVertex } from '@anthropic-ai/vertex-sdk'; +import Anthropic from '@anthropic-ai/sdk'; + +test('body params are not mutated', async () => { + const client = new AnthropicVertex({ + baseURL: 'http://localhost:5000/', + accessToken: 'placeholder', + region: 'us-east-2', + projectId: 'placeholder', + fetch: (url) => { + return Promise.resolve( + new Response(JSON.stringify({ url, custom: true }), { + headers: { 'Content-Type': 'application/json' }, + }), + ); + }, + }); + + const params: Anthropic.MessageCreateParamsNonStreaming = { + model: 'claude-3-opus-latest', + messages: [], + max_tokens: 100, + }; + const original = JSON.stringify(params); + + await client.messages.create(params); + + expect(JSON.stringify(params)).toEqual(original); +}); diff --git a/scripts/test-all b/scripts/test-all new file mode 100755 index 00000000..4da421bb --- /dev/null +++ b/scripts/test-all @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e + +bash ./scripts/test + +for dir in packages/*; do + if [ -f "$dir/scripts/test" ]; then + echo "==> Running tests in $dir" + (cd "$dir" && bash scripts/test) + fi +done