{"id":28946677,"url":"https://github.com/dmitriz/testing-api","last_synced_at":"2026-03-07T01:31:55.166Z","repository":{"id":295069371,"uuid":"986226707","full_name":"dmitriz/testing-api","owner":"dmitriz","description":null,"archived":false,"fork":false,"pushed_at":"2026-02-24T04:53:12.000Z","size":73,"stargazers_count":0,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-24T10:46:38.374Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dmitriz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-05-19T09:45:50.000Z","updated_at":"2025-05-24T13:51:49.000Z","dependencies_parsed_at":"2025-05-23T14:21:18.169Z","dependency_job_id":"8bb8ab59-18ae-42ff-9c84-0666bf54a511","html_url":"https://github.com/dmitriz/testing-api","commit_stats":null,"previous_names":["dmitriz/testing-api"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dmitriz/testing-api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitriz%2Ftesting-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitriz%2Ftesting-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitriz%2Ftesting-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitriz%2Ftesting-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dmitriz","download_url":"https://codeload.github.com/dmitriz/testing-api/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitriz%2Ftesting-api/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30205103,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-06T19:07:06.838Z","status":"ssl_error","status_checked_at":"2026-03-06T18:57:34.882Z","response_time":250,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-23T08:42:03.540Z","updated_at":"2026-03-07T01:31:55.136Z","avatar_url":"https://github.com/dmitriz.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# API Testing with OpenAPI Summary\n\n## 1. Introduction\n\n* **Purpose:** Consolidate discussions on using OpenAPI (Swagger) for API design,\n  documentation, and contract testing.\n* **Focus:** Demonstrate testing a JavaScript function that builds an API request\n  object against an OpenAPI contract without live API calls.\n\n## 2. Core Concepts\n\n### 2.1. OpenAPI Specification (Swagger)\n\n* Standard for describing RESTful APIs.\n* Benefits: Design-first, documentation, code/test generation.\n* Structure: YAML/JSON; key sections: `info`, `servers`, `paths`, `components`.\n\n### 2.2. API Contract Testing\n\n* Definition: Verifying consumer and provider independently adhere to a shared\n  contract.\n* Goal: Reliable integration, reducing need for extensive end-to-end tests.\n* Perspectives: Consumer-side and Provider-side.\n\n### 2.3. OpenAPI as the Contract\n\n* The OpenAPI specification serves as the definitive contract.\n\n## 3. Key Strategy: Validating Request Builder Functions\n\n* **Scenario:** Testing a function that prepares an Axios request configuration\n  object (`reqObj`) but doesn't execute the HTTP call.\n* **Challenge:** Mapping `reqObj` (Axios config) to a standard HTTP request\n  format for OpenAPI validators.\n  * Differences: `method` case, `url` vs. `path`, `params` (query), `data`\n    (body), `headers` case.\n* **Solution:** Use a library (e.g., `openapi-backend` in Node.js) to validate\n  the mapped `reqObj` components against the OpenAPI spec.\n\n## 4. Detailed Example: Contract Testing a Request Builder\n\n### 4.1. Scenario Overview\n\n* Test a JS function (e.g., `buildGetUserProfileRequest`) returning an Axios\n  config.\n* Ensure this config would produce a compliant HTTP request.\n\n### 4.2. File Structure\n\n* `api-spec.yaml`\n* `src/request-builder.js`\n* `src/request-builder.contract.test.js`\n* `package.json`\n\n### 4.3. File Content and Explanation\n\n#### 4.3.1. `api-spec.yaml`\n\n* **Purpose:** Defines the API contract.\n* **Content:**\n\n```yaml\nopenapi: 3.0.0\ninfo:\n  title: Sample API\n  version: 1.0.0\nservers:\n  - url: https://api.example.com/v1\npaths:\n  /users/{userId}/profile:\n    get:\n      summary: Get user profile\n      operationId: getUserProfile\n      parameters:\n        - name: userId\n          in: path\n          required: true\n          schema:\n            type: string\n            format: uuid\n        - name: includeDetails\n          in: query\n          required: false\n          schema:\n            type: boolean\n      responses:\n        '200':\n          description: User profile data\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  id:\n                    type: string\n                    format: uuid\n                  email:\n                    type: string\n                    format: email\n                  displayName:\n                    type: string\n    put:\n      summary: Update user profile\n      operationId: updateUserProfile\n      parameters:\n        - name: userId\n          in: path\n          required: true\n          schema:\n            type: string\n            format: uuid\n      requestBody:\n        required: true\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                email:\n                  type: string\n                  format: email\n                displayName:\n                  type: string\n              required:\n                - email\n      responses:\n        '200':\n          description: Update confirmation\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  message:\n                    type: string\n        '400':\n          description: Invalid input\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  code:\n                    type: integer\n                  message:\n                    type: string\n```\n\n#### 4.3.2. `src/request-builder.js`\n\n* **Purpose:** Contains functions generating Axios request objects.\n* **Content:**\n\n```javascript\n// src/request-builder.js\nconst BASE_URL = 'https://api.example.com/v1';\n\nfunction buildGetUserProfileRequest(userId, includeDetails) {\n    if (!userId) {\n        throw new Error('userId is required');\n    }\n    const reqObj = {\n        method: 'GET',\n        url: `${BASE_URL}/users/${userId}/profile`,\n        headers: {\n            'Accept': 'application/json'\n        },\n        params: {},\n    };\n    if (includeDetails !== undefined) {\n        reqObj.params.includeDetails = includeDetails;\n    }\n    return reqObj;\n}\n\nfunction buildUpdateUserProfileRequest(userId, profileData) {\n     if (!userId) {\n        throw new Error('userId is required');\n    }\n     if (!profileData || !profileData.email) {\n        throw new Error('profileData with email is required');\n    }\n     const reqObj = {\n        method: 'PUT',\n        url: `${BASE_URL}/users/${userId}/profile`,\n        headers: {\n            'Accept': 'application/json',\n            'Content-Type': 'application/json'\n        },\n        data: profileData\n    };\n    return reqObj;\n}\n\nmodule.exports = { buildGetUserProfileRequest, buildUpdateUserProfileRequest };\n```\n\n#### 4.3.3. `tests/request-builder.contract.test.js`\n\n* **Purpose:** Jest test file using `openapi-backend` to validate\n  `request-builder.js` output.\n* **Key Steps:**\n  1. Initialize `openapi-backend` with `api-spec.yaml`.\n  2. Helper function (`mapAxiosReqToValidationReq`) to translate Axios `reqObj`\n     for validator.\n  3. Test cases: Call builder, map `reqObj`, use `api.validateRequest()`, assert\n     validation.\n* **Content:**\n\n```javascript\n// tests/request-builder.contract.test.js\nconst path = require('path');\nconst OpenAPIBackend = require('openapi-backend').default;\nconst {\n    buildGetUserProfileRequest,\n    buildUpdateUserProfileRequest\n} = require('../src/request-builder');\n\ndescribe('Request Builder Contract Tests', () =\u003e {\n    let api;\n\n    beforeAll(async () =\u003e {\n        api = new OpenAPIBackend({\n            definition: path.resolve(__dirname, '../api-spec.yaml'),\n        });\n        await api.init();\n    });\n\n    function mapAxiosReqToValidationReq(reqObj) {\n        const url = new URL(reqObj.url);\n        const lowercasedHeaders = {};\n        if (reqObj.headers) {\n            for (const key in reqObj.headers) {\n                lowercasedHeaders[key.toLowerCase()] = reqObj.headers[key];\n            }\n        }\n        return {\n            method: reqObj.method.toLowerCase(),\n            path: url.pathname,\n            query: reqObj.params,\n            body: reqObj.data,\n            headers: lowercasedHeaders,\n        };\n    }\n\n    test('buildGetUserProfileRequest should produce a valid request object', () =\u003e {\n        const userId = 'a1b2c3d4-e5f6-7890-1234-567890abcdef';\n        const includeDetails = true;\n        const reqObj = buildGetUserProfileRequest(userId, includeDetails);\n        const validationReq = mapAxiosReqToValidationReq(reqObj);\n        const validationResult = api.validateRequest(validationReq);\n        expect(validationResult.valid, \n          `Validation Errors: ${JSON.stringify(validationResult.errors, null, 2)}`)\n          .toBe(true);\n    });\n\n    test('buildUpdateUserProfileRequest should produce a valid request object', () =\u003e {\n        const userId = 'b2c3d4e5-f6a7-8901-2345-67890abcdef1';\n        const profileData = { email: 'test@example.com', displayName: 'Test User' };\n        const reqObj = buildUpdateUserProfileRequest(userId, profileData);\n        const validationReq = mapAxiosReqToValidationReq(reqObj);\n        const validationResult = api.validateRequest(validationReq);\n        expect(validationResult.valid, \n          `Validation Errors: ${JSON.stringify(validationResult.errors, null, 2)}`)\n          .toBe(true);\n    });\n\n    test('buildUpdateUserProfileRequest with missing required body property should fail validation', () =\u003e {\n        const userId = 'c3d4e5f6-a7b8-9012-3456-7890abcdef12';\n        const invalidProfileData = { displayName: 'Another User' }; // Missing 'email'\n        let reqObj;\n        try {\n            reqObj = buildUpdateUserProfileRequest(userId, invalidProfileData); // Builder might throw first\n        } catch (e) {\n            // If builder throws due to its own validation, this path can be asserted differently\n            // For this test, we assume the builder allows it, and spec validation catches it\n            // Or handle this specific builder error assertion here.\n             expect(e.message).toContain('email is required'); // Example if builder throws\n             return;\n        }\n\n        const validationReq = mapAxiosReqToValidationReq(reqObj);\n        const validationResult = api.validateRequest(validationReq);\n        expect(validationResult.valid).toBe(false);\n        expect(validationResult.errors).toEqual(\n            expect.arrayContaining([\n                expect.objectContaining({\n                    message: expect.stringContaining(\"required property 'email'\"),\n                })\n            ])\n        );\n    });\n});\n```\n\n#### 4.3.4. `package.json`\n\n* **Purpose:** Manages project dependencies and scripts.\n* **Key Dependencies:** `jest`, `openapi-backend`.\n* **Content:**\n\n```json\n{\n  \"name\": \"api-request-builder-contract-test-example\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Example of contract testing an Axios request builder function using OpenAPI.\",\n  \"main\": \"src/request-builder.js\",\n  \"directories\": {\n    \"test\": \"tests\"\n  },\n  \"scripts\": {\n    \"test\": \"jest\"\n  },\n  \"keywords\": [\n    \"openapi\",\n    \"swagger\",\n    \"contract-testing\",\n    \"axios\",\n    \"jest\",\n    \"openapi-backend\"\n  ],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"jest\": \"^29.7.0\",\n    \"openapi-backend\": \"^5.9.2\"\n  }\n}\n```\n\n### 4.4. How to Run\n\n1. Ensure Node.js and npm are installed.\n2. Create the file structure (`src/`, `tests/`) and files as above.\n3. Run `npm install` in the root directory (where `package.json` is).\n4. Run `npm test`.\n\n## 5. Broader Application (e.g., LLM Registry)\n\n* This contract testing approach is valuable if any provider in a registry (like\n  an LLM registry) exposes a standard REST API defined by an OpenAPI spec for\n  which request objects are being built.\n* For SDK-based providers (e.g., Genkit), testing typically involves mocking the\n  SDK's methods rather than HTTP contract validation.\n\n## 6. Conclusion\n\n* Validating request builder functions against an OpenAPI contract ensures\n  structural correctness before network calls.\n* This enhances reliability and helps catch integration issues early.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmitriz%2Ftesting-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdmitriz%2Ftesting-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmitriz%2Ftesting-api/lists"}