diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..58cd3ba --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,82 @@ +name: Continuous Integration / Pull Request + +on: + pull_request: + branches: + - master + +jobs: + lint: + name: Lint Code + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # -------- pnpm + cache ---------- + - uses: pnpm/action-setup@v4 + with: + version: 8 # Specify pnpm version (adjust to your preferred version) + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + # Try with --frozen-lockfile first, fallback to regular install if it fails + - name: Install dependencies + run: pnpm install --frozen-lockfile || pnpm install + + - name: Run ESLint + run: pnpm run lint + + build: + name: Build (Node ${{ matrix.node-version }}) + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # -------- pnpm + cache ---------- + - uses: pnpm/action-setup@v4 + with: + version: 8 # Specify pnpm version + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'pnpm' + + # Try with --frozen-lockfile first, fallback to regular install if it fails + - name: Install dependencies + run: pnpm install --frozen-lockfile || pnpm install + + - name: Build SDK + run: pnpm run build + + test: + name: Run Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # -------- pnpm + cache ---------- + - uses: pnpm/action-setup@v4 + with: + version: 8 # Specify pnpm version + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + # Try with --frozen-lockfile first, fallback to regular install if it fails + - name: Install dependencies + run: pnpm install --frozen-lockfile || pnpm install + + - name: Run Jest tests + run: pnpm run test \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7621472 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +name: Release + +on: + workflow_dispatch: + +concurrency: + group: release + cancel-in-progress: true + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: write # needed for pushing version commits & creating releases + issues: write # needed for commenting on issues + pull-requests: write # needed for commenting on PRs + id-token: write # needed for npm provenance + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - uses: pnpm/action-setup@v4 + with: + version: 8 # Specify pnpm version + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + registry-url: 'https://registry.npmjs.org' + provenance: true + + # Try with --frozen-lockfile first, fallback to regular install if it fails + - name: Install dependencies + run: pnpm install --frozen-lockfile || pnpm install + + - run: pnpm run test + + - run: pnpm run build + + - name: Install semantic-release + run: pnpm add -D semantic-release @semantic-release/changelog @semantic-release/git conventional-changelog-conventionalcommits + + - name: Run semantic-release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_PROVENANCE: true + run: npx semantic-release \ No newline at end of file diff --git a/.github/workflows/semantics.yml b/.github/workflows/semantics.yml new file mode 100644 index 0000000..ebf2f04 --- /dev/null +++ b/.github/workflows/semantics.yml @@ -0,0 +1,39 @@ +name: Pull Request Semantics + +on: + pull_request: + branches: + - master + types: [opened, edited, reopened, synchronize] + +jobs: + semantics: + name: Semantics + runs-on: ubuntu-latest + permissions: + pull-requests: read + contents: read + steps: + - name: Validate PR Title + run: | + PR_TITLE="${{ github.event.pull_request.title }}" + echo "Validating PR title: $PR_TITLE" + + # Install commitlint + npm init -y + npm install --save-dev @commitlint/cli @commitlint/config-angular + + # Configure commitlint + echo "module.exports = { \ + extends: ['@commitlint/config-angular'], \ + rules: { \ + 'subject-empty': [2, 'never'], \ + 'scope-empty': [0, 'never'], \ + 'type-case': [2, 'always', 'lower-case'], \ + 'type-empty': [2, 'never'], \ + 'type-enum': [2, 'always', ['chore','feat','fix','refactor']] \ + } \ + };" > commitlint.config.js + + # Validate title + echo "$PR_TITLE" | npx commitlint diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..c7fc8f9 --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,16 @@ +{ + "branches": ["master"], + "plugins": [ + "@semantic-release/commit-analyzer", + ["@semantic-release/release-notes-generator", { "preset": "conventionalcommits" }], + "@semantic-release/changelog", + ["@semantic-release/npm", { + "provenance": true + }], + ["@semantic-release/git", { + "assets": ["CHANGELOG.md", "package.json", "pnpm-lock.yaml"], + "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + }], + "@semantic-release/github" + ] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..19c6575 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# Changelog + +All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. \ No newline at end of file diff --git a/test/integration/deployments.real.int.test.ts b/test/integration/deployments.real.int.test.ts deleted file mode 100644 index e413bd6..0000000 --- a/test/integration/deployments.real.int.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -// import * as nock from 'nock'; // Removed nock -import { v4 as uuidv4 } from 'uuid'; -import { ApiError, AuthError, OpenApiSource, SpecInvalidError, TadataNodeSDK } from '../../src'; - -// More realistic OpenAPI spec with a path -const validOpenApiSpec = { - openapi: '3.0.0', - info: { - title: 'Test API', - version: '1.0.0', - }, - paths: { - '/test': { - get: { - summary: 'Test endpoint', - responses: { - '200': { - description: 'OK', - }, - }, - }, - }, - }, -}; - -const invalidOpenApiSpec = { - info: { - title: 'Test API', - version: '1.0.0', - }, - paths: {}, -}; - -// Use your real API key here (this is an example, replace with your actual key) -const REAL_API_KEY = 'fc2b51fef0f646aabe533f895a58ec64a61bdb77fbd81693f908339d2312cb7e'; -const INVALID_API_KEY = 'invalid_api_key'; - -// Can remove this constant since it's not needed anymore -// const MOCKED_DEPLOYMENT_NAME = 'Mocked Test API Service'; - -describe('Deployments Integration Test (Real Backend)', () => { - // Extend timeout for real API calls - jest.setTimeout(60000); - - const sdk = new TadataNodeSDK({ - apiKey: REAL_API_KEY, - }); - - const invalidSdk = new TadataNodeSDK({ - apiKey: INVALID_API_KEY, - }); - - // Removed nock related beforeAll, afterEach, afterAll - - // First test to verify backend connection is working - it('should verify backend is running by rejecting invalid credentials', async () => { - const source = OpenApiSource.fromObject(validOpenApiSpec); - - await expect( - invalidSdk.mcp.deploy({ - spec: source, - name: 'Auth Test', - }) - ).rejects.toThrow(AuthError); - }); - - describe('when deploying an OpenAPI spec', () => { - it('should successfully deploy an MCP server', async () => { - const source = OpenApiSource.fromObject(validOpenApiSpec); - - const result = await sdk.mcp.deploy({ - spec: source, - name: `Test API Service ${uuidv4()}`, - specBaseUrl: 'https://example.com/api', - }); - - // Validate the response structure - expect(result).toBeDefined(); - expect(result.id).toBeDefined(); - expect(result.specVersion).toBeDefined(); - expect(result.createdAt).toBeInstanceOf(Date); - - const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - expect(uuidRegex.test(result.id)).toBe(true); - }); - - it('should throw SpecInvalidError for invalid OpenAPI specs before making API call', () => { - expect(() => OpenApiSource.fromObject(invalidOpenApiSpec)).toThrow(SpecInvalidError); - }); - - it('should throw AuthError when using incorrect API key', async () => { - const source = OpenApiSource.fromObject(validOpenApiSpec); - - await expect( - invalidSdk.mcp.deploy({ - spec: source, - name: 'Auth Test Service (Real Backend)', - }) - ).rejects.toThrow(AuthError); - }); - - it('should throw ApiError for backend validation failures', async () => { - // Using a spec that should trigger validation failures on the backend - const invalidSource = OpenApiSource.fromObject({ - openapi: '3.0.0', - info: { title: 'Invalid API Spec', version: '1.0.0' }, - // Empty paths object with no endpoints - paths: {}, - }); - - await expect( - sdk.mcp.deploy({ - spec: invalidSource, - name: 'Backend Validation Error Test', - }) - ).rejects.toThrow(ApiError); - }); - - it('should throw SpecInvalidError for malformed request body', async () => { - const source = OpenApiSource.fromObject(validOpenApiSpec); - - await expect( - sdk.mcp.deploy({ - spec: source, - // @ts-expect-error - intentionally passing wrong type for testing - name: 12345, - }) - ).rejects.toThrow(SpecInvalidError); - }); - - it('should throw ApiError for not found errors (404)', async () => { - const source = OpenApiSource.fromObject(validOpenApiSpec); - - // To trigger a 404, we need to hit a non-existent endpoint - // We'll do this by modifying the request to include a specific ID that doesn't exist - await expect( - sdk.mcp.deploy({ - spec: source, - name: 'Not Found Test', - // @ts-expect-error - intentionally passing a property that doesn't exist to trigger 404 - id: 'non-existent-id', - }) - ).rejects.toThrow(ApiError); - }); - - // Keep this test skipped as it's hard to reliably trigger a 500 error - it.skip('should throw ApiError for server errors (e.g., 500)', async () => { - // This test is skipped because it's difficult to reliably trigger a 500 error - // on the real backend without specific server conditions - }); - }); -}); diff --git a/test/utils/index.ts b/test/utils/index.ts index 169cb6f..0f2842a 100644 --- a/test/utils/index.ts +++ b/test/utils/index.ts @@ -1 +1 @@ -export * from './response-helpers'; \ No newline at end of file +export * from './response-helpers'; diff --git a/test/utils/response-helpers.ts b/test/utils/response-helpers.ts index 18b2f66..8b22414 100644 --- a/test/utils/response-helpers.ts +++ b/test/utils/response-helpers.ts @@ -1,4 +1,4 @@ -import { ErrorCode } from '../../src'; +import { ErrorCode } from '../../src/http/schemas'; /** * Helper to create a success response envelope @@ -55,4 +55,4 @@ export function createErrorResponse(error: { } return response; -} \ No newline at end of file +}