Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Demo commit #3

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
test(api): add tests generated by Chapter
  • Loading branch information
chapter-test-bot authored and render committed Feb 19, 2025
commit 6a4cfcffeb2fcc9630a27109f1d485bb16a940b9
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import axios, { AxiosInstance, AxiosResponse } from 'axios';
import { describe, beforeAll, test, expect } from '@jest/globals';

// Example interfaces based on the provided OpenAPI schema references.
// Adjust or enrich these according to your actual schemas.
interface SucceedResponse {
success: boolean;
message: string;
}

interface ErrorResponse {
error: string;
message: string;
}

// Utility function for checking if a string is empty or has only whitespace.
// Used to test edge cases with path parameters.
function isEmptyOrWhitespace(str: string): boolean {
return !str || !str.trim();
}

// Test suite for DELETE /api/v1/projects/{projectRef}/envvars/{env}/{name}
describe('DELETE /api/v1/projects/{projectRef}/envvars/{env}/{name}', () => {
let axiosInstance: AxiosInstance;

// Set up axios instance before tests.
beforeAll(() => {
axiosInstance = axios.create({
baseURL: process.env.API_BASE_URL,
// Let the test handle status codes, so we set validateStatus to always return true.
validateStatus: () => true,
});
});

test('Should delete environment variable successfully (200)', async () => {
// Arrange
const projectRef = 'my-project';
const env = 'development';
const name = 'TEST_VAR';

// Act
const response: AxiosResponse<SucceedResponse | ErrorResponse> = await axiosInstance.delete(
`/api/v1/projects/${projectRef}/envvars/${env}/${name}`,
{
headers: {
'Authorization': `Bearer ${process.env.API_AUTH_TOKEN}`,
},
}
);

// Assert: 200 Success
// The API specification indicates a 200 response, but if the server returns 200 or 204, adapt the check.
expect([200]).toContain(response.status);
expect(response.headers['content-type']).toContain('application/json');

if (response.status === 200) {
// Validate that the response body conforms to SucceedResponse if status is 200.
const data = response.data as SucceedResponse;
expect(data).toHaveProperty('success');
expect(data).toHaveProperty('message');
}
});

test('Should return 400 (Or 422) for invalid path parameters', async () => {
// Arrange: Use invalid path parameters (e.g., empty or malformed)
const invalidProjectRef = '';
const invalidEnv = '';
const invalidName = '';

// Act
const response: AxiosResponse<ErrorResponse> = await axiosInstance.delete(
`/api/v1/projects/${invalidProjectRef}/envvars/${invalidEnv}/${invalidName}`,
{
headers: {
'Authorization': `Bearer ${process.env.API_AUTH_TOKEN}`,
},
}
);

// Assert: Expect 400 or 422 for invalid input
// The API may return 400 or 422 for invalid payload or path parameters.
expect([400, 422]).toContain(response.status);
expect(response.headers['content-type']).toContain('application/json');

// Validate error response structure
const data = response.data;
expect(data).toHaveProperty('error');
expect(data).toHaveProperty('message');

// Further checks on the error content could be done here.
});

test('Should return 401 or 403 if authorization token is missing or invalid', async () => {
// Arrange
const projectRef = 'my-project';
const env = 'development';
const name = 'ANOTHER_TEST_VAR';

// Act
// Intentionally omit or use an invalid token.
const response: AxiosResponse<ErrorResponse> = await axiosInstance.delete(
`/api/v1/projects/${projectRef}/envvars/${env}/${name}`
// No headers provided to simulate missing Authorization
);

// Assert: Check 401 or 403. API may return either for unauthorized/forbidden.
expect([401, 403]).toContain(response.status);
expect(response.headers['content-type']).toContain('application/json');

// Validate error response.
const data = response.data;
expect(data).toHaveProperty('error');
expect(data).toHaveProperty('message');
});

test('Should return 404 for non-existent environment variable', async () => {
// Arrange
const projectRef = 'my-project';
const env = 'development';
const name = 'NON_EXISTENT_VAR';

// Act
const response: AxiosResponse<ErrorResponse> = await axiosInstance.delete(
`/api/v1/projects/${projectRef}/envvars/${env}/${name}`,
{
headers: {
'Authorization': `Bearer ${process.env.API_AUTH_TOKEN}`,
},
}
);

// Assert: Expect 404 if the resource is not found.
expect(response.status).toBe(404);
expect(response.headers['content-type']).toContain('application/json');

// Validate error response.
const data = response.data;
expect(data).toHaveProperty('error');
expect(data).toHaveProperty('message');
});

test('Should handle extremely large path parameters gracefully (edge case)', async () => {
// Arrange: Create an extremely large string.
const largeString = 'x'.repeat(5000); // 5,000 characters
const projectRef = largeString;
const env = largeString;
const name = largeString;

const response: AxiosResponse<ErrorResponse> = await axiosInstance.delete(
`/api/v1/projects/${projectRef}/envvars/${env}/${name}`,
{
headers: {
'Authorization': `Bearer ${process.env.API_AUTH_TOKEN}`,
},
}
);

// The API might respond with 400, 414 (URI Too Long), or similar.
// If it does not handle large inputs, it could return a server error (500+).
// Adjust expectations based on actual API behavior.
expect([400, 414, 422, 500]).toContain(response.status);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import axios, { AxiosInstance, AxiosResponse } from 'axios';
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';

/**
* Below tests focus on DELETE /api/v1/schedules/{schedule_id}
* using Jest (test framework) + axios (HTTP client) in TypeScript.
*
* Make sure to:
* 1. Set process.env.API_BASE_URL to your API base URL.
* 2. Set process.env.API_AUTH_TOKEN to a valid auth token for authorization.
* 3. Provide a valid IMPERATIVE schedule ID below if you want to test 200 success.
* (or create one in a setup step if needed.)
*/

// Example schedule IDs for testing. Modify these to valid/invalid values in your environment.
// The validScheduleId should reference an existing "IMPERATIVE" schedule.
const validScheduleId = 'YOUR_VALID_IMPERATIVE_SCHEDULE_ID';
// A schedule ID that does not exist.
const nonExistentScheduleId = 'nonexistent-schedule-id';
// A malformed schedule ID.
const invalidScheduleId = '!!!';

// Utility function to create an Axios instance.
function createApiClient(token?: string): AxiosInstance {
return axios.create({
baseURL: process.env.API_BASE_URL,
headers: {
'Content-Type': 'application/json',
Authorization: token ? `Bearer ${token}` : '',
},
validateStatus: () => true, // allow us to handle status codes ourselves
});
}

// Main test suite
describe('DELETE /api/v1/schedules/{schedule_id}', () => {
let apiClient: AxiosInstance;

beforeAll(() => {
// Create a client with valid auth token
apiClient = createApiClient(process.env.API_AUTH_TOKEN);
});

// 1. Input Validation: Missing or invalid parameters

it('should return 404 (or possibly 400) when schedule_id is empty', async () => {
// Attempt to DELETE with an empty schedule ID (effectively /api/v1/schedules/)
const response: AxiosResponse = await apiClient.delete('/api/v1/schedules/');
// Depending on the server’s configuration, this might return 404, 400, or another error.
// We expect an error since the path is incomplete.
expect(response.status).toBeGreaterThanOrEqual(400);
// Some servers might treat it as 404 Not Found.
// If your API returns 400 or 422 for invalid path params, adapt expectations accordingly.
});

it('should return 400 or 422 for a malformed schedule_id', async () => {
const response: AxiosResponse = await apiClient.delete(`/api/v1/schedules/${invalidScheduleId}`);
// Many APIs will respond with 400 or 422 for invalid ID formats.
expect([400, 422]).toContain(response.status);
});

// 2. Response Validation (200 success, 404 Not Found, etc.)

it('should delete schedule successfully (200) for a valid IMPERATIVE schedule_id', async () => {
// If validScheduleId references an existing schedule, we expect a 200.
// This test will fail if that schedule does not exist.
const response: AxiosResponse = await apiClient.delete(`/api/v1/schedules/${validScheduleId}`);
// Check status code
expect(response.status).toBe(200);
// If the response body has a schema, verify required fields.
// e.g., expect(response.data).toHaveProperty('message', 'Schedule deleted successfully');

// 3. Response Headers Validation
expect(response.headers['content-type']).toMatch(/application\/json/i);
// If other headers are relevant, check them here.
});

it('should return 404 Not Found for a non-existent schedule_id', async () => {
const response: AxiosResponse = await apiClient.delete(`/api/v1/schedules/${nonExistentScheduleId}`);
expect(response.status).toBe(404);
// Optionally check the response body for error details, if defined.
// e.g. expect(response.data).toHaveProperty('error', 'Resource not found');
});

// 4. Edge Case & Limit Testing

it('should handle extremely large schedule_id gracefully', async () => {
const largeScheduleId = 'a'.repeat(1000); // artificially large string
const response: AxiosResponse = await apiClient.delete(`/api/v1/schedules/${largeScheduleId}`);
// Could be 400, 404, or 414 (URI Too Long) depending on server config.
expect(response.status).toBeGreaterThanOrEqual(400);
});

// 5. Testing Authorization & Authentication

it('should return 401 or 403 when no auth token is provided', async () => {
const unauthorizedClient = createApiClient(); // no token
const response: AxiosResponse = await unauthorizedClient.delete(`/api/v1/schedules/${validScheduleId}`);
expect([401, 403]).toContain(response.status);
});

it('should return 401 or 403 for an invalid auth token', async () => {
const invalidAuthClient = createApiClient('invalid-token');
const response: AxiosResponse = await invalidAuthClient.delete(`/api/v1/schedules/${validScheduleId}`);
expect([401, 403]).toContain(response.status);
});

// Additional tests could be added for server errors (e.g., 500) if you have a way to trigger them.

afterAll(() => {
// Cleanup or restore any resources if needed
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import axios, { AxiosInstance } from 'axios';\nimport { describe, it, beforeAll, afterAll, expect } from '@jest/globals';\n\ndescribe('GET /api/v1/projects/:projectRef/envvars/:env/:name', () => {\n let client: AxiosInstance;\n const validProjectRef = 'my-project';\n const validEnv = 'production';\n const validName = 'SECRET_KEY';\n const invalidProjectRef = '';\n const invalidEnv = '';\n const invalidName = '';\n\n beforeAll(() => {\n const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:3000';\n const API_AUTH_TOKEN = process.env.API_AUTH_TOKEN || 'test-token';\n\n client = axios.create({\n baseURL: API_BASE_URL,\n headers: {\n Authorization: 'Bearer ' + API_AUTH_TOKEN,\n 'Content-Type': 'application/json',\n },\n validateStatus: () => true, // We'll handle status checks in tests\n });\n });\n\n afterAll(() => {\n // any cleanup if needed\n });\n\n describe('Valid Request Scenarios', () => {\n it('should retrieve environment variable when valid parameters are provided', async () => {\n const response = await client.get(\n '/api/v1/projects/' + validProjectRef + '/envvars/' + validEnv + '/' + validName\n );\n\n expect(response.status).toBe(200);\n expect(response.headers['content-type']).toContain('application/json');\n // Validate response body structure\n // For demonstration, we do minimal checks; ideally, you check fields from #/components/schemas/EnvVarValue\n expect(response.data).toHaveProperty('name');\n expect(response.data.name).toBe(validName);\n });\n });\n\n describe('Input Validation', () => {\n it('should return 400 or 422 for an invalid projectRef', async () => {\n const response = await client.get(\n '/api/v1/projects/' + invalidProjectRef + '/envvars/' + validEnv + '/' + validName\n );\n\n expect([400, 422]).toContain(response.status);\n expect(response.headers['content-type']).toContain('application/json');\n // check error schema\n expect(response.data).toHaveProperty('error');\n });\n\n it('should return 400 or 422 for an invalid env', async () => {\n const response = await client.get(\n '/api/v1/projects/' + validProjectRef + '/envvars/' + invalidEnv + '/' + validName\n );\n\n expect([400, 422]).toContain(response.status);\n expect(response.headers['content-type']).toContain('application/json');\n expect(response.data).toHaveProperty('error');\n });\n\n it('should return 400 or 422 for an invalid name', async () => {\n const response = await client.get(\n '/api/v1/projects/' + validProjectRef + '/envvars/' + validEnv + '/' + invalidName\n );\n\n expect([400, 422]).toContain(response.status);\n expect(response.headers['content-type']).toContain('application/json');\n expect(response.data).toHaveProperty('error');\n });\n });\n\n describe('Unauthorized & Forbidden Requests', () => {\n it('should return 401 or 403 when no authorization token is provided', async () => {\n const response = await axios.get(\n (process.env.API_BASE_URL || 'http://localhost:3000') +\n '/api/v1/projects/' + validProjectRef + '/envvars/' + validEnv + '/' + validName\n ); // no auth header\n\n expect([401, 403]).toContain(response.status);\n });\n\n it('should return 401 or 403 when an invalid authorization token is provided', async () => {\n const clientWithInvalidToken = axios.create({\n baseURL: process.env.API_BASE_URL || 'http://localhost:3000',\n headers: {\n Authorization: 'Bearer invalid_token',\n 'Content-Type': 'application/json',\n },\n validateStatus: () => true,\n });\n const response = await clientWithInvalidToken.get(\n '/api/v1/projects/' + validProjectRef + '/envvars/' + validEnv + '/' + validName\n );\n\n expect([401, 403]).toContain(response.status);\n });\n });\n\n describe('Resource Not Found', () => {\n it('should return 404 if the environment variable does not exist', async () => {\n const nonExistentName = 'NON_EXISTENT_VAR';\n const response = await client.get(\n '/api/v1/projects/' + validProjectRef + '/envvars/' + validEnv + '/' + nonExistentName\n );\n\n expect(response.status).toBe(404);\n expect(response.headers['content-type']).toContain('application/json');\n expect(response.data).toHaveProperty('error');\n });\n\n it('should return 404 if the projectRef does not exist', async () => {\n const fakeProjectRef = 'fakeProject';\n const response = await client.get(\n '/api/v1/projects/' + fakeProjectRef + '/envvars/' + validEnv + '/' + validName\n );\n\n expect(response.status).toBe(404);\n expect(response.headers['content-type']).toContain('application/json');\n expect(response.data).toHaveProperty('error');\n });\n });\n\n describe('Response Headers Validation', () => {\n it('should include general and security headers in the response', async () => {\n const response = await client.get(\n '/api/v1/projects/' + validProjectRef + '/envvars/' + validEnv + '/' + validName\n );\n\n expect(response.status).toBe(200);\n expect(response.headers['content-type']).toContain('application/json');\n // if the API includes caching or rate-limiting headers, check them\n // e.g., expect(response.headers).toHaveProperty('cache-control');\n // e.g., expect(response.headers).toHaveProperty('x-ratelimit-limit');\n });\n });\n\n describe('Edge Case & Stress Testing', () => {\n it('should handle extremely long envvar name gracefully (expecting 400/422 or 404)', async () => {\n const longName = 'A'.repeat(1024);\n const response = await client.get(\n '/api/v1/projects/' + validProjectRef + '/envvars/' + validEnv + '/' + longName\n );\n\n // Depending on implementation, might be 400, 422, or 404\n expect([400, 422, 404]).toContain(response.status);\n });\n });\n});
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import axios, { AxiosError, AxiosRequestConfig } from 'axios';
import { describe, it, expect } from '@jest/globals';

// Load environment variables
const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:3000';
const API_AUTH_TOKEN = process.env.API_AUTH_TOKEN || '';

// Utility function to create an Axios instance
function createApiClient(token?: string) {
const config: AxiosRequestConfig = {
baseURL: API_BASE_URL,
headers: {},
validateStatus: () => true, // We'll handle status code checks manually
};

if (token) {
config.headers = {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
};
}

return axios.create(config);
}

// Example data for valid path parameter values
// Adjust these appropriately for your API's valid data
const VALID_PROJECT_REF = 'exampleProject';
const VALID_ENV = 'production';

// Example data for invalid path parameter values
const INVALID_PROJECT_REF = ''; // empty string
const INVALID_ENV = '!!!invalid-env!!!';
const NON_EXISTENT_PROJECT_REF = 'nonExistentProject';
const NON_EXISTENT_ENV = 'unknownEnv';

// Helper function to check if response headers match the expected values
function validateResponseHeaders(headers: any) {
expect(headers).toBeDefined();
// Content-Type should be application/json.
expect(headers['content-type']).toContain('application/json');
// Add other header checks here if needed, e.g., Cache-Control, X-RateLimit, etc.
}

// Example minimal schema validation for the 200 response.
// In a real test suite, you would validate against the actual
// #/components/schemas/ListEnvironmentVariablesResponse schema.
function validateListEnvironmentVariablesResponse(data: any) {
// Example check: data might be an array of environment variables, or an object containing them
// Adjust these checks to match your actual schema.
expect(data).toBeDefined();
// If the response is expected to have a property like "environmentVariables" that is an array:
// expect(Array.isArray(data.environmentVariables)).toBe(true);
// For now, just check if data is an object or array.
expect(typeof data === 'object' || Array.isArray(data)).toBeTruthy();
}

// Example minimal schema validation for an ErrorResponse.
function validateErrorResponse(data: any) {
// Adjust according to your actual #/components/schemas/ErrorResponse schema.
expect(data).toHaveProperty('error');
expect(typeof data.error).toBe('string');
}

describe('GET /api/v1/projects/{projectRef}/envvars/{env} - List environment variables', () => {
it('should return 200 and a valid response for valid path parameters', async () => {
const client = createApiClient(API_AUTH_TOKEN);

const response = await client.get(
`/api/v1/projects/${VALID_PROJECT_REF}/envvars/${VALID_ENV}`
);

// Expect 200 OK
expect(response.status).toBe(200);

// Validate headers
validateResponseHeaders(response.headers);

// Validate response body schema
validateListEnvironmentVariablesResponse(response.data);
});

it('should return 401 or 403 when no auth token is provided', async () => {
const client = createApiClient();

const response = await client.get(
`/api/v1/projects/${VALID_PROJECT_REF}/envvars/${VALID_ENV}`
);

// Expect unauthorized or forbidden
expect([401, 403]).toContain(response.status);

// When unauthorized or forbidden, we expect an error response body
validateErrorResponse(response.data);
});

it('should return 401 or 403 when an invalid auth token is provided', async () => {
const client = createApiClient('invalid_token');

const response = await client.get(
`/api/v1/projects/${VALID_PROJECT_REF}/envvars/${VALID_ENV}`
);

// Expect unauthorized or forbidden
expect([401, 403]).toContain(response.status);

// Validate error response body
validateErrorResponse(response.data);
});

it('should return 400 or 422 if path parameters are invalid format', async () => {
// Example: an empty projectRef or an obviously invalid env name
const client = createApiClient(API_AUTH_TOKEN);

const response1 = await client.get(
`/api/v1/projects/${INVALID_PROJECT_REF}/envvars/${VALID_ENV}`
);
// The API might return 400 or 422 for invalid input
expect([400, 422]).toContain(response1.status);
validateErrorResponse(response1.data);

const response2 = await client.get(
`/api/v1/projects/${VALID_PROJECT_REF}/envvars/${INVALID_ENV}`
);
// The API might return 400 or 422 for invalid input
expect([400, 422]).toContain(response2.status);
validateErrorResponse(response2.data);
});

it('should return 404 if the projectRef or env does not exist', async () => {
const client = createApiClient(API_AUTH_TOKEN);

const response1 = await client.get(
`/api/v1/projects/${NON_EXISTENT_PROJECT_REF}/envvars/${VALID_ENV}`
);
// Expect 404 when projectRef is not found
expect(response1.status).toBe(404);
validateErrorResponse(response1.data);

const response2 = await client.get(
`/api/v1/projects/${VALID_PROJECT_REF}/envvars/${NON_EXISTENT_ENV}`
);
// Expect 404 when environment is not found
expect(response2.status).toBe(404);
validateErrorResponse(response2.data);
});

it('should handle requests that might produce an empty list gracefully (if applicable)', async () => {
// In case the environment is valid but has no environment variables.
// Adjust if your API returns 200 with an empty array or a special response.

const client = createApiClient(API_AUTH_TOKEN);

// This test assumes that "emptyEnv" is a valid environment with no variables.
// You can adjust the environment name or the projectRef to produce an empty list.
const response = await client.get(
`/api/v1/projects/${VALID_PROJECT_REF}/envvars/emptyEnv`
);

// Even if no variables exist, it should still be a 200, returning an empty list.
// Or if your API returns 404 if no env vars exist, adjust accordingly.
expect([200, 404]).toContain(response.status);

if (response.status === 200) {
validateResponseHeaders(response.headers);
// Validate schema (likely an empty array or an object with empty array)
validateListEnvironmentVariablesResponse(response.data);
// Additional check if it returns an empty array
// expect(response.data.environmentVariables).toHaveLength(0);
} else {
// 404 scenario
validateErrorResponse(response.data);
}
});

it('should handle unexpected server error gracefully (500)', async () => {
// In many cases, forcing a 500 error can be challenging. This test scenario might be
// more hypothetical and depends on how your server triggers 500 errors.
// You might need to mock or simulate a server condition that returns 500.

// For demonstration, assume that using a special projectRef triggers a 500 in your test environment.
const projectRefCausingServerError = 'trigger500';
const client = createApiClient(API_AUTH_TOKEN);

const response = await client.get(
`/api/v1/projects/${projectRefCausingServerError}/envvars/${VALID_ENV}`
);

// Expect 500 or some other server error code
if (response.status >= 500 && response.status < 600) {
// Expecting server error responses
expect(true).toBe(true);
} else {
// If your API does not actually return 500 in test, just log it.
console.warn('Server did not produce a 500 error as expected.');
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import axios, { AxiosError } from 'axios';

describe('GET /api/v1/projects/{projectRef}/runs', () => {
const baseURL = process.env.API_BASE_URL || 'http://localhost:3000';
const token = process.env.API_AUTH_TOKEN || '';
const validProjectRef = 'my-valid-project';
const invalidProjectRef = '!@#'; // some invalid reference
const path = '/api/v1/projects';

beforeAll(() => {
if (!baseURL) {
console.warn('API_BASE_URL is not set. Tests may fail.');
}
});

describe('Input Validation', () => {
it('should return 200 for valid query parameters', async () => {
const url = `${baseURL}${path}/${validProjectRef}/runs?status=completed&page=1&limit=5`;
try {
const response = await axios.get(url, {
headers: {
Authorization: `Bearer ${token}`,
},
});
expect(response.status).toBe(200);
// Additional assertions about response structure could go here
} catch (error) {
// If there's an error, force the test to fail
throw new Error(`Unexpected error: ${error}`);
}
});

it('should return 400 or 422 for invalid query parameter types', async () => {
const url = `${baseURL}${path}/${validProjectRef}/runs?limit=abc`; // limit is invalid type
try {
await axios.get(url, {
headers: {
Authorization: `Bearer ${token}`,
},
});
fail('Expected request to fail');
} catch (error) {
const axiosError = error as AxiosError;
expect([400, 422]).toContain(axiosError.response?.status);
}
});
});

describe('Response Validation', () => {
it('should return valid JSON and status 200 for a successful request', async () => {
const url = `${baseURL}${path}/${validProjectRef}/runs`;
const response = await axios.get(url, {
headers: {
Authorization: `Bearer ${token}`,
},
});
expect(response.status).toBe(200);
expect(response.headers['content-type']).toContain('application/json');
// Here we can do partial schema validation:
expect(response.data).toHaveProperty('runs');
expect(Array.isArray(response.data.runs)).toBe(true);
});

it('should handle invalid projectRef gracefully', async () => {
const url = `${baseURL}${path}/${invalidProjectRef}/runs`;
try {
await axios.get(url, {
headers: {
Authorization: `Bearer ${token}`,
},
});
fail('Expected request to fail due to invalid projectRef');
} catch (error) {
const axiosError = error as AxiosError;
// The API might return 400 or 404 if the projectRef is not valid
expect([400, 404]).toContain(axiosError.response?.status);
}
});
});

describe('Response Headers Validation', () => {
it('should have correct Content-Type header', async () => {
const url = `${baseURL}${path}/${validProjectRef}/runs`;
const response = await axios.get(url, {
headers: {
Authorization: `Bearer ${token}`,
},
});
expect(response.headers['content-type']).toContain('application/json');
});
});

describe('Edge Case & Limit Testing', () => {
it('should return empty array if no runs are found', async () => {
const emptyProjectRef = 'project-with-no-runs';
const url = `${baseURL}${path}/${emptyProjectRef}/runs`;
const response = await axios.get(url, {
headers: {
Authorization: `Bearer ${token}`,
},
});
expect(response.status).toBe(200);
expect(Array.isArray(response.data.runs)).toBe(true);
expect(response.data.runs.length).toBe(0);
});

it('should return 401 or 403 if token is missing', async () => {
const url = `${baseURL}${path}/${validProjectRef}/runs`;
try {
await axios.get(url); // no authorization header
fail('Expected request to fail due to missing token');
} catch (error) {
const axiosError = error as AxiosError;
expect([401, 403]).toContain(axiosError.response?.status);
}
});

it('should return 401 or 403 if token is invalid', async () => {
const url = `${baseURL}${path}/${validProjectRef}/runs`;
try {
await axios.get(url, {
headers: {
Authorization: 'Bearer invalid-token',
},
});
fail('Expected request to fail due to invalid token');
} catch (error) {
const axiosError = error as AxiosError;
expect([401, 403]).toContain(axiosError.response?.status);
}
});
});
});
192 changes: 192 additions & 0 deletions chapter_api_tests/2024-04/validation/test_get_api-v1-runs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import axios, { AxiosInstance, AxiosResponse } from 'axios';
import { config as loadEnv } from 'dotenv';
import { describe, beforeAll, afterAll, it, expect } from '@jest/globals';

// Load environment variables
loadEnv();

// Create an axios instance for our tests
let apiClient: AxiosInstance;

beforeAll(() => {
apiClient = axios.create({
baseURL: process.env.API_BASE_URL,
headers: {
Authorization: `Bearer ${process.env.API_AUTH_TOKEN}`,
'Content-Type': 'application/json',
},
});
});

afterAll(() => {
// Any cleanup logic can go here
});

describe('GET /api/v1/runs - List runs', () => {
it('should return 200 and a valid response body for a valid request with default parameters', async () => {
const response: AxiosResponse = await apiClient.get('/api/v1/runs');

// Response status
expect(response.status).toBe(200);

// Response headers
expect(response.headers['content-type']).toContain('application/json');

// Basic body validation (assuming the response returns an object with "runs")
expect(response.data).toHaveProperty('runs');
// Further validation can be performed if the OpenAPI schema is available.
});

it('should handle valid pagination query parameters (cursorPagination) and return 200', async () => {
// Example: Using page/limit or "cursor" style pagination if applicable
const params = {
limit: 5,
page: 1,
};

const response: AxiosResponse = await apiClient.get('/api/v1/runs', {
params,
});

expect(response.status).toBe(200);
expect(response.headers['content-type']).toContain('application/json');
expect(response.data).toHaveProperty('runs');
// Optionally check if returned "runs" length is <= limit
});

it('should filter runs by valid filter parameters (runsFilter) and return 200', async () => {
// Example status: "completed", version: "1.0.0"
const params = {
status: 'completed',
version: '1.0.0',
};

const response: AxiosResponse = await apiClient.get('/api/v1/runs', {
params,
});

expect(response.status).toBe(200);
expect(response.headers['content-type']).toContain('application/json');
expect(response.data).toHaveProperty('runs');
// Validate if the returned data actually matches the filter criteria if test data is known
});

it('should return an empty list if no matches are found for a given filter', async () => {
const params = {
status: 'nonexistent-status',
};

const response: AxiosResponse = await apiClient.get('/api/v1/runs', {
params,
});

expect(response.status).toBe(200);
expect(response.headers['content-type']).toContain('application/json');
expect(response.data).toHaveProperty('runs');
// Expect possibly an empty array
expect(Array.isArray(response.data.runs)).toBe(true);
// If the system returns an empty array for no matches:
expect(response.data.runs.length).toBe(0);
});

it('should return 400 or 422 for invalid query parameter types', async () => {
try {
// Passing a string where a number is expected, e.g., limit = 'invalid'
await apiClient.get('/api/v1/runs', {
params: {
limit: 'invalid',
},
});
// If it doesn’t throw, force fail
fail('Expected an error for invalid query parameters.');
} catch (error: any) {
// The API may return 400 or 422 in this scenario
const status = error.response?.status;
expect([400, 422]).toContain(status);
}
});

it('should return 401 or 403 when authorization token is invalid', async () => {
const invalidClient = axios.create({
baseURL: process.env.API_BASE_URL,
headers: {
Authorization: 'Bearer invalid_token',
'Content-Type': 'application/json',
},
});

try {
await invalidClient.get('/api/v1/runs');
fail('Expected an unauthorized or forbidden error.');
} catch (error: any) {
const status = error.response?.status;
expect([401, 403]).toContain(status);
}
});

it('should return 401 or 403 when authorization header is missing', async () => {
const noAuthClient = axios.create({
baseURL: process.env.API_BASE_URL,
});

try {
await noAuthClient.get('/api/v1/runs');
fail('Expected an unauthorized or forbidden error.');
} catch (error: any) {
const status = error.response?.status;
expect([401, 403]).toContain(status);
}
});

it('should return 400 if request includes malformed query parameter', async () => {
try {
await apiClient.get('/api/v1/runs', {
params: {
status: '', // Possibly an empty string if status must be non-empty
},
});
// If no error, force fail
fail('Expected a 400 or 422 error for malformed query parameter.');
} catch (error: any) {
const status = error.response?.status;
expect([400, 422]).toContain(status);
}
});

it('should return 404 for a non-existing endpoint', async () => {
try {
await apiClient.get('/api/v1/runs-nonexisting');
fail('Expected a 404 Not Found error.');
} catch (error: any) {
// Some APIs might return 404 or 400, but typically 404 is expected for a missing route
expect(error.response?.status).toBe(404);
}
});

it('should handle server errors (5xx) gracefully if they occur', async () => {
// This test simulates or checks the API’s behavior for server-side errors.
// Without a real way to force a 5xx error, we typically rely on error conditions in local/dev environment.
// You might skip this test or simulate a scenario if your test environment can trigger a server error.
// Example is shown here for completeness:

try {
// Attempt a request that might trigger a server-side error
await apiClient.get('/api/v1/runs', {
params: {
causeServerError: true, // If the API has some debug flag (this is hypothetical)
},
});
fail('Expected a 5xx server error.');
} catch (error: any) {
const status = error.response?.status;
// Commonly, status would be 500 or maybe 503
if (status) {
expect(status).toBeGreaterThanOrEqual(500);
expect(status).toBeLessThan(600);
} else {
// If no status is returned, we fail the test
fail('Expected a 5xx server error, but none was received.');
}
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import axios, { AxiosResponse } from 'axios';
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';

/**
* Jest test suite for GET /api/v1/schedules/{schedule_id}
*
* Requirements Covered:
* 1. Input Validation
* 2. Response Validation
* 3. Response Headers Validation
* 4. Edge Case & Limit Testing
* 5. Authorization & Authentication
*
* Notes:
* - The base URL is loaded from the environment variable: API_BASE_URL
* - The auth token is loaded from the environment variable: API_AUTH_TOKEN
* - Since GET endpoints typically do not have a request body, payload-related tests here focus on path parameters.
* - For invalid/missing parameters, the API may return 400 or 422. Both are acceptable.
* - For unauthorized/forbidden requests, the API may return 401 or 403. Both are acceptable.
*/

describe('GET /api/v1/schedules/{schedule_id}', () => {
let baseURL: string;
let validToken: string;
let axiosInstance = axios.create();

beforeAll(() => {
baseURL = process.env.API_BASE_URL || 'http://localhost:3000';
validToken = process.env.API_AUTH_TOKEN || '';
});

afterAll(() => {
// Clean up or tear down if needed
});

/**
* Helper function to perform GET request.
*/
const getSchedule = async (
scheduleId: string | number | undefined,
token: string | undefined
): Promise<AxiosResponse<any>> => {
// Construct URL. If scheduleId is missing or invalid, that tests error behaviors.
const url = scheduleId
? `${baseURL}/api/v1/schedules/${scheduleId}`
: `${baseURL}/api/v1/schedules/`; // Intentionally missing ID

return axiosInstance.get(url, {
headers: token
? {
Authorization: `Bearer ${token}`,
}
: {},
});
};

describe('1. Input Validation', () => {
it('should return 400 or 422 if schedule_id is missing', async () => {
try {
await getSchedule(undefined, validToken);
// If the request does not fail, force a failure.
fail('Expected an error for missing schedule_id, but request succeeded.');
} catch (error: any) {
expect([400, 422, 404]).toContain(error?.response?.status);
// Depending on implementation, 404 might also be returned.
}
});

it('should return 400 or 422 if schedule_id is invalid (wrong type)', async () => {
// Passing a number where string is expected, for instance
try {
await getSchedule(12345, validToken);
fail('Expected an error for invalid schedule_id, but request succeeded.');
} catch (error: any) {
expect([400, 422, 404]).toContain(error?.response?.status);
}
});

it('should handle empty string as schedule_id', async () => {
try {
await getSchedule('', validToken);
fail('Expected an error for empty schedule_id, but request succeeded.');
} catch (error: any) {
expect([400, 422, 404]).toContain(error?.response?.status);
}
});
});

describe('2. Response Validation', () => {
it('should retrieve a schedule (200) with a valid schedule_id', async () => {
// Example known valid schedule ID
const scheduleId = 'sched_1234';

const response = await getSchedule(scheduleId, validToken);
expect(response.status).toBe(200);
// Check response body structure — assuming at least it has an "id" field
expect(response.data).toBeDefined();
expect(typeof response.data).toBe('object');
expect(response.data).toHaveProperty('id', scheduleId);
});

it('should return 404 when the schedule_id is not found', async () => {
const nonExistentId = 'sched_does_not_exist';
try {
await getSchedule(nonExistentId, validToken);
fail('Expected a 404 error for non-existent schedule, but request succeeded.');
} catch (error: any) {
// 404 expected for resource not found
expect(error?.response?.status).toBe(404);
}
});
});

describe('3. Response Headers Validation', () => {
it('should include Content-Type: application/json for a valid request', async () => {
const scheduleId = 'sched_1234';
const response = await getSchedule(scheduleId, validToken);

expect(response.headers).toBeDefined();
expect(response.headers['content-type']).toContain('application/json');
});
});

describe('4. Edge Case & Limit Testing', () => {
it('should handle extremely large schedule_id gracefully (likely 400, 422, or 404)', async () => {
const largeScheduleId = 'sched_' + 'x'.repeat(1000); // very large ID
try {
await getSchedule(largeScheduleId, validToken);
fail('Expected an error for extremely large schedule_id, but request succeeded.');
} catch (error: any) {
// Depending on implementation details, it might return 400, 422, or 404.
expect([400, 422, 404]).toContain(error?.response?.status);
}
});

// GET requests typically do not return empty arrays unless the resource is a collection
// but we check if 404 is returned instead of an empty response if the ID is not found.
it('should return 404 instead of an empty object/array if the schedule is not found', async () => {
const nonExistentId = 'sched_nonexistent';
try {
await getSchedule(nonExistentId, validToken);
fail('Expected a 404 for non-existent schedule, got success.');
} catch (error: any) {
expect(error?.response?.status).toBe(404);
}
});

it('should handle server error (5xx) gracefully if it occurs', async () => {
// This test is conceptual; if the server is not mocked to produce 5xx,
// you can catch the scenario if any unhandled error occurs.
// We'll simulate by using an unrealistic endpoint.
try {
await axiosInstance.get(`${baseURL}/api/v1/schedules/trigger-500-error`);
// If no error, we can skip.
} catch (error: any) {
// If a 500 occurs, test is satisfied.
if (error?.response?.status === 500) {
expect(error?.response?.status).toBe(500);
}
}
});
});

describe('5. Testing Authorization & Authentication', () => {
it('should return 401 or 403 if the request is made without a token', async () => {
const scheduleId = 'sched_1234';
try {
await getSchedule(scheduleId, undefined);
fail('Expected a 401/403 error for missing token, but request succeeded.');
} catch (error: any) {
expect([401, 403]).toContain(error?.response?.status);
}
});

it('should return 401 or 403 if the token is invalid', async () => {
const scheduleId = 'sched_1234';
const invalidToken = 'invalid_token';
try {
await getSchedule(scheduleId, invalidToken);
fail('Expected a 401/403 error for invalid token, but request succeeded.');
} catch (error: any) {
expect([401, 403]).toContain(error?.response?.status);
}
});
});
});
216 changes: 216 additions & 0 deletions chapter_api_tests/2024-04/validation/test_get_api-v1-schedules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import axios, { AxiosError, AxiosResponse } from 'axios';
import { describe, it, expect, beforeAll } from '@jest/globals';

/*************************************************
* Test Suite for GET /api/v1/schedules
* Method: GET
* Path: /api/v1/schedules
* Description: List all schedules with optional pagination.
*************************************************/

describe('GET /api/v1/schedules', () => {
let baseURL: string;
let token: string;

beforeAll(() => {
// Load environment variables
baseURL = process.env.API_BASE_URL || 'http://localhost:3000';
token = process.env.API_AUTH_TOKEN || '';
});

/**
* Helper function to create an axios instance with common headers
*/
const getAxiosInstance = (authToken?: string) => {
return axios.create({
baseURL,
headers: {
'Content-Type': 'application/json',
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
},
validateStatus: () => true, // We handle status checks manually
});
};

/**
* 1. Input Validation
* - Test valid/invalid query params.
*/
it('should return 200 OK with valid query parameters (page, perPage)', async () => {
const instance = getAxiosInstance(token);

const response: AxiosResponse = await instance.get('/api/v1/schedules', {
params: {
page: 1,
perPage: 10,
},
});

// Expect a 200 status for valid query params
expect(response.status).toBe(200);
// Check for application/json header
expect(response.headers['content-type']).toContain('application/json');
// Check for a valid response body structure (partial, as example)
expect(response.data).toBeDefined();
// Example: If the schema includes a schedules array, verify
// Adjust the property checks below to match the actual schema from #/components/schemas/ListSchedulesResult
// expect(Array.isArray(response.data.schedules)).toBe(true);
});

it('should allow no query parameters and return 200 with a default page result', async () => {
const instance = getAxiosInstance(token);

const response: AxiosResponse = await instance.get('/api/v1/schedules');

expect(response.status).toBe(200);
expect(response.headers['content-type']).toContain('application/json');
expect(response.data).toBeDefined();
// Perform partial schema checks
});

it('should return 400 or 422 when page parameter is invalid (e.g., a string)', async () => {
const instance = getAxiosInstance(token);

const response: AxiosResponse = await instance.get('/api/v1/schedules', {
params: {
page: 'invalid',
},
});

// The API may return 400 or 422 for invalid parameters.
expect([400, 422]).toContain(response.status);
expect(response.headers['content-type']).toContain('application/json');
});

it('should return 400 or 422 when perPage parameter is invalid (e.g., negative)', async () => {
const instance = getAxiosInstance(token);

const response: AxiosResponse = await instance.get('/api/v1/schedules', {
params: {
perPage: -10,
},
});

expect([400, 422]).toContain(response.status);
expect(response.headers['content-type']).toContain('application/json');
});

/**
* 2. Response Validation
* - Validate 200 success structure, error codes, etc.
*/
it('should match the expected 200 response schema for a valid request', async () => {
const instance = getAxiosInstance(token);
const response: AxiosResponse = await instance.get('/api/v1/schedules');

expect(response.status).toBe(200);
expect(response.headers['content-type']).toContain('application/json');

// Example partial schema validation
// Adjust to match #/components/schemas/ListSchedulesResult
// expect(Array.isArray(response.data.schedules)).toBe(true);
// expect(response.data.page).toBeDefined();
// expect(response.data.total).toBeDefined();
});

/**
* 3. Response Headers Validation
* - Check "Content-Type", etc.
*/
it('should include Content-Type header in the response', async () => {
const instance = getAxiosInstance(token);

const response: AxiosResponse = await instance.get('/api/v1/schedules');

expect(response.status).toBe(200);
expect(response.headers['content-type']).toContain('application/json');
});

/**
* 4. Edge Case & Limit Testing
*/
it('should return an empty array or valid structure if no schedules exist (edge case)', async () => {
// This test presumes a scenario where the database might be empty.
// If your environment always has schedules, adapt as needed.
const instance = getAxiosInstance(token);

const response: AxiosResponse = await instance.get('/api/v1/schedules', {
params: {
page: 999999, // Large page number may return an empty list
perPage: 100,
},
});

// Expect a successful response with potential empty data.
expect(response.status).toBe(200);
expect(response.data).toBeDefined();
// Adjust property checks based on your actual response schema.
// Example:
// expect(Array.isArray(response.data.schedules)).toBe(true);
// expect(response.data.schedules.length).toBe(0);
});

it('should ensure proper handling with extremely large perPage value', async () => {
const instance = getAxiosInstance(token);

const response: AxiosResponse = await instance.get('/api/v1/schedules', {
params: {
perPage: 999999999, // A large integer to test boundaries.
},
});

// The API might still return 200 or possibly 400 if it's out of range.
// Adjust expectations based on your API's behavior.
expect([200, 400, 422]).toContain(response.status);
});

it('should handle server errors gracefully if the server returns a 500 (hypothetical test)', async () => {
// This test assumes you might force a 500 error by some specific input or environment.
// Adjust or remove this if 500 is not easily triggered.
const instance = getAxiosInstance(token);

// Example only: Not guaranteed to trigger a 500.
// In a real environment, you might have a special test setup to provoke a server error.
try {
const response = await instance.get('/api/v1/schedules', {
params: {
page: -999999, // Possibly invalid enough to cause a server error in some implementations.
},
});
// If the server does not return 500, check acceptable alternate statuses.
expect([200, 400, 422]).toContain(response.status);
} catch (err) {
const error = err as AxiosError;
// Check if we indeed got a 500
if (error.response) {
expect(error.response.status).toBe(500);
}
}
});

/**
* 5. Testing Authorization & Authentication
* - Test valid, invalid, and missing credentials.
*/
it('should return 200 with a valid token', async () => {
// Assuming token is valid if provided.
if (!token) {
console.warn('No valid API_AUTH_TOKEN found; skipping test.');
return;
}

const instance = getAxiosInstance(token);
const response: AxiosResponse = await instance.get('/api/v1/schedules');

expect(response.status).toBe(200);
expect(response.headers['content-type']).toContain('application/json');
});

it('should return 401 or 403 for missing or invalid token', async () => {
const instance = getAxiosInstance('InvalidOrMissingToken');
const response: AxiosResponse = await instance.get('/api/v1/schedules');

// The API might return 401 or 403.
expect([401, 403]).toContain(response.status);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import axios from 'axios';\n\ndescribe('GET /api/v1/timezones', () => {\n const baseURL = process.env.API_BASE_URL || '';\n const authToken = process.env.API_AUTH_TOKEN || '';\n let axiosInstance;\n\n beforeAll(() => {\n axiosInstance = axios.create({\n baseURL,\n validateStatus: () => true, // allow non-2xx responses so we can test error conditions\n headers: {\n // Use template literals for authorization header\n Authorization: `Bearer ${authToken}`\n }\n });\n });\n\n it('should return 200 and valid JSON when excludeUtc is not provided', async () => {\n const response = await axiosInstance.get('/api/v1/timezones');\n expect(response.status).toBe(200);\n // Response Headers Validation\n expect(response.headers['content-type']).toContain('application/json');\n // Response Body Validation\n expect(response.data).toBeDefined();\n // Assuming the response schema includes an array property named timezones\n expect(Array.isArray(response.data.timezones)).toBe(true);\n });\n\n it('should return 200 with excludeUtc=true', async () => {\n const response = await axiosInstance.get('/api/v1/timezones?excludeUtc=true');\n expect(response.status).toBe(200);\n expect(response.headers['content-type']).toContain('application/json');\n expect(Array.isArray(response.data.timezones)).toBe(true);\n // Additional checks to confirm UTC is excluded if needed.\n });\n\n it('should return 200 with excludeUtc=false', async () => {\n const response = await axiosInstance.get('/api/v1/timezones?excludeUtc=false');\n expect(response.status).toBe(200);\n expect(response.headers['content-type']).toContain('application/json');\n expect(Array.isArray(response.data.timezones)).toBe(true);\n // Additional checks to confirm UTC is included if needed.\n });\n\n it('should return 400 or 422 if excludeUtc is invalid type', async () => {\n // Providing an invalid string in place of a boolean should yield 400 or 422.\n const response = await axiosInstance.get('/api/v1/timezones?excludeUtc=abc');\n expect([400, 422]).toContain(response.status);\n });\n\n it('should return 401 or 403 if no auth token is provided', async () => {\n const noAuthInstance = axios.create({\n baseURL,\n validateStatus: () => true\n });\n const response = await noAuthInstance.get('/api/v1/timezones');\n expect([401, 403]).toContain(response.status);\n });\n\n it('should return 401 or 403 if invalid auth token is provided', async () => {\n const invalidAuthInstance = axios.create({\n baseURL,\n validateStatus: () => true,\n headers: {\n Authorization: 'Bearer invalidTokenHere'\n }\n });\n const response = await invalidAuthInstance.get('/api/v1/timezones');\n expect([401, 403]).toContain(response.status);\n });\n});\n
184 changes: 184 additions & 0 deletions chapter_api_tests/2024-04/validation/test_get_api-v3-runs-{runId}.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import axios, { AxiosInstance } from 'axios';
import { AxiosResponse } from 'axios';

/**
* Jest test suite for GET /api/v3/runs/{runId}
*
* Environment variables:
* - API_BASE_URL: Base URL of the API (e.g. https://api.example.com)
* - API_AUTH_TOKEN: Authentication token (can be public or secret key)
* - API_PUBLIC_TOKEN: (Optional) Another token to test public-key behavior.
*
* Note: This test suite demonstrates various scenarios for input validation,
* response validation, response headers, edge cases, and authentication.
* Replace the placeholder run IDs with real or mock values for your actual testing.
*/

describe('GET /api/v3/runs/{runId}', () => {
let client: AxiosInstance;

const baseURL = process.env.API_BASE_URL;
const secretToken = process.env.API_AUTH_TOKEN; // Presumed secret key
const publicToken = process.env.API_PUBLIC_TOKEN; // Optionally used for public-key tests

// Placeholder run IDs. Replace these with actual/valid IDs for integration tests.
const validRunId = 'valid-run-id';
const nonExistentRunId = 'non-existent-run-id';
const invalidRunId = '!!!'; // Malformed run ID

beforeAll(() => {
// Create an axios instance with baseURL
client = axios.create({
baseURL,
timeout: 15000, // 15 seconds
validateStatus: () => true, // Let us handle the status code checks in tests.
});
});

/**
* Helper to make the GET request.
* @param runId The run ID to retrieve.
* @param token The authorization token.
*/
const getRun = async (runId: string, token?: string): Promise<AxiosResponse> => {
const headers: Record<string, string> = {};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}

return client.get(`/api/v3/runs/${runId}`, {
headers,
});
};

/********************************************************************************
* 1. INPUT VALIDATION TESTS
********************************************************************************/

it('should return 400 or 422 for invalid run ID format', async () => {
// For an obviously invalid runId, the API may respond with 400 or 422.
const response = await getRun(invalidRunId, secretToken);

// Check that the status is either 400 or 422
expect([400, 422]).toContain(response.status);

// Optionally check the error message structure
if (response.status === 400 || response.status === 422) {
expect(response.data).toHaveProperty('error');
}
});

it('should return 404 if run does not exist', async () => {
// The API may respond with 404 if the run is not found.
const response = await getRun(nonExistentRunId, secretToken);

expect(response.status).toBe(404);
expect(response.data).toHaveProperty('error', 'Run not found');
});

/********************************************************************************
* 2. RESPONSE VALIDATION
********************************************************************************/

it('should return 200 and match the expected schema for a valid run ID (secret token)', async () => {
// Assuming the validRunId refers to an existing run.
const response = await getRun(validRunId, secretToken);

expect(response.status).toBe(200);
// Check for presence of required fields in the response.
// The actual schema keys may differ based on your OpenAPI definitions.
// For example:
expect(response.data).toHaveProperty('id');
expect(response.data).toHaveProperty('status');
expect(response.data).toHaveProperty('payload');
expect(response.data).toHaveProperty('output');
expect(response.data).toHaveProperty('attempts');

// Additional checks for field types, etc.
// e.g., expect(typeof response.data.status).toBe('string');
});

/********************************************************************************
* 3. RESPONSE HEADERS VALIDATION
********************************************************************************/

it('should include appropriate response headers for a valid run', async () => {
const response = await getRun(validRunId, secretToken);

// Verify status code.
expect(response.status).toBe(200);

// Check Content-Type header is application/json.
expect(response.headers['content-type']).toContain('application/json');

// Check for other optional headers like Cache-Control or Rate-Limit.
// Example:
// expect(response.headers).toHaveProperty('cache-control');
// expect(response.headers).toHaveProperty('x-ratelimit-limit');
});

/********************************************************************************
* 4. EDGE CASE & LIMIT TESTING
********************************************************************************/

it('should return 401 or 403 if no auth token is provided', async () => {
// Missing token scenario.
const response = await getRun(validRunId);

// The API might return 401 or 403.
expect([401, 403]).toContain(response.status);

// Optional: Check error message.
if (response.status === 401) {
expect(response.data).toHaveProperty('error', 'Invalid or Missing API key');
} else if (response.status === 403) {
// Some APIs might differentiate.
// Check the error structure if relevant.
}
});

it('should handle extremely large or malformed run ID gracefully', async () => {
const largeRunId = 'a'.repeat(1000); // A very long run ID.
const response = await getRun(largeRunId, secretToken);

// Expecting a client or server validation error.
expect([400, 422, 404]).toContain(response.status);
});

// This test simulates checking no results found scenario, though for GET by ID,
// a non-existent run might be the typical scenario. Already tested with 404.

/********************************************************************************
* 5. AUTHENTICATION & AUTHORIZATION TESTS
********************************************************************************/

it('should omit payload and output when using a public token (if applicable)', async () => {
// Only run this test if a public token is defined in environment.
if (!publicToken) {
console.warn('No public token found. Skipping public-key test.');
return;
}

const response = await getRun(validRunId, publicToken);

// Expect success with status 200 if the run ID is valid.
// But the payload and output should be omitted.
expect(response.status).toBe(200);

// Expect the presence of other fields.
expect(response.data).toHaveProperty('id');
expect(response.data).toHaveProperty('status');

// The "payload" and "output" fields should be omitted for public key requests.
expect(response.data).not.toHaveProperty('payload');
expect(response.data).not.toHaveProperty('output');
});

it('should return 401 or 403 for an invalid or expired token', async () => {
const invalidToken = 'Bearer invalid-or-expired-token';
const response = await getRun(validRunId, invalidToken);

// Expect either 401 or 403.
expect([401, 403]).toContain(response.status);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
```typescript
import axios, { AxiosInstance, AxiosResponse } from 'axios';
import { describe, it, expect, beforeAll } from '@jest/globals';
/************************************************************
* Jest test suite for:
* POST /api/v1/projects/{projectRef}/envvars/{env}/import
*
* This test suite covers:
* 1. Input Validation
* 2. Response Validation
* 3. Response Headers Validation
* 4. Edge Case & Limit Testing
* 5. Testing Authorization & Authentication
************************************************************/
describe('POST /api/v1/projects/{projectRef}/envvars/{env}/import', () => {
let client: AxiosInstance;
let validProjectRef = 'example-project-123';
let validEnv = 'development';
beforeAll(() => {
const baseURL = process.env.API_BASE_URL || 'http://localhost:3000';
const token = process.env.API_AUTH_TOKEN || '';
client = axios.create({
baseURL,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
validateStatus: () => true, // Allow handling status codes in tests
});
});
/************************************************************
* 1) Successful upload of environment variables (200 OK)
************************************************************/
it('should upload environment variables successfully (200)', async () => {
// Example of a valid request body based on an assumed schema:
// {
// vars: [
// { key: 'VAR_KEY', value: 'VAR_VALUE' },
// ...
// ]
// }
const validRequestBody = {
vars: [
{ key: 'TEST_KEY', value: 'TEST_VALUE' },
{ key: 'ANOTHER_KEY', value: 'ANOTHER_VALUE' },
],
};
const response: AxiosResponse = await client.post(
`/api/v1/projects/${validProjectRef}/envvars/${validEnv}/import`,
validRequestBody
);
// Response Validation
expect([200]).toContain(response.status);
expect(response.data).toBeDefined();
// Example: Check if response follows success structure
// Adjust property checks based on your actual schema
// For instance, if SucceedResponse has a "message" field:
// expect(response.data).toHaveProperty('message');
// Response Headers Validation
expect(response.headers['content-type']).toMatch(/application\/json/);
});
/************************************************************
* 2) Invalid request body -> expect 400 or 422
************************************************************/
it('should return 400 or 422 for invalid request body', async () => {
const invalidRequestBody = {
// Missing or malformed "vars" field
// e.g., string instead of array
vars: "this-should-be-an-array",
};
const response: AxiosResponse = await client.post(
`/api/v1/projects/${validProjectRef}/envvars/${validEnv}/import`,
invalidRequestBody
);
// Expecting 400 or 422 for invalid payload
expect([400, 422]).toContain(response.status);
expect(response.data).toBeDefined();
// Check if error response structure matches expectations (e.g., error details)
// Example:
// expect(response.data).toHaveProperty('error');
// Response Headers Validation
expect(response.headers['content-type']).toMatch(/application\/json/);
});
/************************************************************
* 3) Unauthorized or forbidden -> expect 401 or 403
************************************************************/
it('should return 401 or 403 when the Authorization token is missing or invalid', async () => {
// Create a client without auth token
const unauthorizedClient = axios.create({
baseURL: process.env.API_BASE_URL || 'http://localhost:3000',
headers: {
'Content-Type': 'application/json',
},
validateStatus: () => true,
});
const validRequestBody = {
vars: [{ key: 'KEY_NO_AUTH', value: 'VALUE_NO_AUTH' }],
};
const response: AxiosResponse = await unauthorizedClient.post(
`/api/v1/projects/${validProjectRef}/envvars/${validEnv}/import`,
validRequestBody
);
// Expecting 401 or 403
expect([401, 403]).toContain(response.status);
// Check if response content matches expected structure
// For example:
// expect(response.data).toMatchObject({ error: expect.any(String) });
});
/************************************************************
* 4) Resource not found -> expect 404
************************************************************/
it('should return 404 when projectRef or env does not exist', async () => {
const invalidProjectRef = 'non-existing-project';
const invalidEnv = 'non-existing-env';
const validRequestBody = {
vars: [{ key: 'TEST_KEY_404', value: 'TEST_VALUE_404' }],
};
const response: AxiosResponse = await client.post(
`/api/v1/projects/${invalidProjectRef}/envvars/${invalidEnv}/import`,
validRequestBody
);
// Expecting 404
expect(response.status).toBe(404);
// Check response structure if applicable
// Example:
// expect(response.data).toHaveProperty('error');
// Response Headers Validation
expect(response.headers['content-type']).toMatch(/application\/json/);
});
/************************************************************
* 5) Edge case: Empty request body
* - Depending on API constraints, expect 400, 422, or success if empty is allowed
************************************************************/
it('should handle empty request body', async () => {
const emptyRequestBody = {};
const response: AxiosResponse = await client.post(
`/api/v1/projects/${validProjectRef}/envvars/${validEnv}/import`,
emptyRequestBody
);
// Expecting 400 or 422 if empty requests are invalid
// or possibly 200 if the API allows an empty import
expect([200, 400, 422]).toContain(response.status);
// If success is valid for empty imports, we can check success structure;
// otherwise, check error.
// expect(response.data).toHaveProperty('error'); or similar.
expect(response.headers['content-type']).toMatch(/application\/json/);
});
/************************************************************
* 6) Edge case: Large payload
* - Test the API handling of large imports.
************************************************************/
it('should handle a large payload of environment variables', async () => {
// Creating a large array of environment variables
const largeVars = Array.from({ length: 1000 }, (_, index) => ({
key: `LARGE_KEY_${index}`,
value: `LARGE_VALUE_${index}`,
}));
const largeRequestBody = {
vars: largeVars,
};
const response: AxiosResponse = await client.post(
`/api/v1/projects/${validProjectRef}/envvars/${validEnv}/import`,
largeRequestBody
);
// Expect success or appropriate handling (e.g., 413 if the payload is too large)
expect([200, 400, 413, 422]).toContain(response.status);
expect(response.headers['content-type']).toMatch(/application\/json/);
});
/************************************************************
* 7) Malformed request (simulate server error handling)
* - If relevant, we can test for 500 or other 5xx.
************************************************************/
it('should handle server errors (simulate a malformed request that leads to 500)', async () => {
// This test depends on whether the server can produce a 500
// For demonstration, we pass an obviously incorrect structure.
const malformedBody = {
vars: 12345, // Not an array or object structure as expected
};
const response: AxiosResponse = await client.post(
`/api/v1/projects/${validProjectRef}/envvars/${validEnv}/import`,
malformedBody
);
// Some servers may return 400 or 422 instead of 500 for malformed bodies.
// If your server can return 500, adjust the test accordingly.
expect([400, 422, 500]).toContain(response.status);
expect(response.headers['content-type']).toMatch(/application\/json/);
});
});
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import axios, { AxiosResponse } from 'axios';
import { describe, it, expect, beforeAll } from '@jest/globals';

// Load environment variables
const API_BASE_URL = process.env.API_BASE_URL;
const API_AUTH_TOKEN = process.env.API_AUTH_TOKEN;

// Common test data
const VALID_PROJECT_REF = 'test-project';
const VALID_ENV = 'production';

// Construct endpoint
// Example: https://example.com/api/v1/projects/test-project/envvars/production
function getEndpoint(projectRef: string, env: string): string {
return `${API_BASE_URL}/api/v1/projects/${projectRef}/envvars/${env}`;
}

// Valid body payload based on presumed schema for creating an environment variable.
// Adjust fields as needed to match your actual API schema.
const validRequestBody = {
name: 'MY_VARIABLE',
value: 'someValue',
};

// Helper function to make requests
async function makeRequest(
projectRef: string,
env: string,
data: any,
token: string | undefined = API_AUTH_TOKEN
): Promise<AxiosResponse> {
const url = getEndpoint(projectRef, env);

const headers: Record<string, string> = {
'Content-Type': 'application/json',
};

if (token) {
headers.Authorization = `Bearer ${token}`;
}

return axios.post(url, data, { headers });
}

describe('POST /api/v1/projects/{projectRef}/envvars/{env}', () => {
beforeAll(() => {
if (!API_BASE_URL) {
throw new Error('API_BASE_URL environment variable is not defined.');
}
});

describe('Input Validation', () => {
it('should create environment variable with valid data (200 response)', async () => {
const response = await makeRequest(VALID_PROJECT_REF, VALID_ENV, validRequestBody);
expect(response.status).toBe(200);
expect(response.headers['content-type']).toContain('application/json');
// Check if response body matches expected schema (e.g., has "success" or similar)
// Adjust this validation to fit the actual "SucceedResponse" schema.
expect(response.data).toHaveProperty('success');
expect(typeof response.data.success).toBe('boolean');
});

it('should return 400 or 422 when required fields are missing', async () => {
// Missing "name" and "value"
const invalidBody = {};
try {
await makeRequest(VALID_PROJECT_REF, VALID_ENV, invalidBody);
} catch (error: any) {
const status = error.response?.status;
// Either 400 or 422 is acceptable for invalid payload
expect([400, 422]).toContain(status);
expect(error.response.data).toBeDefined();
}
});

it('should return 400 or 422 when fields have wrong types', async () => {
// Provide number instead of string
const invalidBody = {
name: 1234,
value: 5678,
};
try {
await makeRequest(VALID_PROJECT_REF, VALID_ENV, invalidBody);
} catch (error: any) {
const status = error.response?.status;
expect([400, 422]).toContain(status);
expect(error.response.data).toBeDefined();
}
});

it('should handle empty string as a field value and potentially return 400 or 422', async () => {
const invalidBody = {
name: '',
value: ''
};
try {
await makeRequest(VALID_PROJECT_REF, VALID_ENV, invalidBody);
} catch (error: any) {
const status = error.response?.status;
expect([400, 422]).toContain(status);
expect(error.response.data).toBeDefined();
}
});
});

describe('Response Validation', () => {
it('should return the correct success structure for valid inputs', async () => {
const response = await makeRequest(VALID_PROJECT_REF, VALID_ENV, validRequestBody);
expect(response.status).toBe(200);
expect(response.headers['content-type']).toContain('application/json');
// Validate response body structure against the expected schema
expect(response.data).toHaveProperty('success');
expect(typeof response.data.success).toBe('boolean');
});

it('should return 404 if projectRef or environment does not exist', async () => {
const nonExistentProjectRef = 'non-existent-project';

try {
await makeRequest(nonExistentProjectRef, VALID_ENV, validRequestBody);
} catch (error: any) {
expect([404]).toContain(error.response?.status);
expect(error.response.data).toBeDefined();
}
});

// Note: The API might return 500 for server errors, or some other code.
// This is a placeholder test in case the server triggers a 5xx.
it('should handle unexpected server errors gracefully (simulate 500)', async () => {
// Simulation approach: if the API doesn’t let you force a 500 easily,
// you might skip or externally test this scenario.
// For demonstration, we assume an invalid environment name triggers a 500 in some rare scenario.
const invalidEnv = 'simulate-500';
try {
await makeRequest(VALID_PROJECT_REF, invalidEnv, validRequestBody);
} catch (error: any) {
// You might replace this logic depending on how your API surfaces errors.
expect([500]).toContain(error.response?.status);
}
});
});

describe('Response Headers Validation', () => {
it('should include application/json in Content-Type for successful request', async () => {
const response = await makeRequest(VALID_PROJECT_REF, VALID_ENV, validRequestBody);
expect(response.headers['content-type']).toContain('application/json');
});

// Add more header checks as needed, e.g. X-RateLimit, Cache-Control, etc.
it('should include standard headers (e.g., Cache-Control) if applicable', async () => {
const response = await makeRequest(VALID_PROJECT_REF, VALID_ENV, validRequestBody);
// Example check:
// expect(response.headers['cache-control']).toBeDefined();
// Adjust based on your API’s actual headers.
expect(response.status).toBe(200);
});
});

describe('Edge Case & Limit Testing', () => {
it('should handle extremely large payload (potentially 413 or 400)', async () => {
// Create a large string
const largeString = 'x'.repeat(100000); // 100k characters
const largePayload = {
name: largeString,
value: largeString,
};

try {
await makeRequest(VALID_PROJECT_REF, VALID_ENV, largePayload);
} catch (error: any) {
// Depending on how the server handles large payloads:
// could be 413 Payload Too Large, 400, or 422
expect([400, 413, 422]).toContain(error.response?.status);
}
});

it('should return proper response when payload is empty', async () => {
try {
await makeRequest(VALID_PROJECT_REF, VALID_ENV, null);
} catch (error: any) {
expect([400, 422]).toContain(error.response?.status);
}
});
});

describe('Testing Authorization & Authentication', () => {
it('should return 401 or 403 when no auth token is provided', async () => {
try {
await makeRequest(VALID_PROJECT_REF, VALID_ENV, validRequestBody, undefined /* no token */);
} catch (error: any) {
const status = error.response?.status;
// The API could return either 401 or 403.
expect([401, 403]).toContain(status);
}
});

it('should return 401 or 403 when auth token is invalid', async () => {
try {
await makeRequest(VALID_PROJECT_REF, VALID_ENV, validRequestBody, 'invalid-token');
} catch (error: any) {
const status = error.response?.status;
expect([401, 403]).toContain(status);
}
});

it('should succeed (200) with a valid auth token', async () => {
const response = await makeRequest(VALID_PROJECT_REF, VALID_ENV, validRequestBody, API_AUTH_TOKEN);
expect(response.status).toBe(200);
expect(response.data).toHaveProperty('success');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import axios, { AxiosResponse } from 'axios';
import { describe, it, expect } from '@jest/globals';

// These environment variables should be set in your test environment.
// Example:
// API_BASE_URL=https://your-api-endpoint.com
// API_AUTH_TOKEN=someValidAuthToken
const BASE_URL = process.env.API_BASE_URL || 'http://localhost:3000';
const AUTH_TOKEN = process.env.API_AUTH_TOKEN || '';

// Helper function to create configured axios instance.
// We disable axios' default status throwing so we can test response codes explicitly.
function createAxiosInstance(token?: string) {
return axios.create({
baseURL: BASE_URL,
validateStatus: () => true, // Let us handle response codes manually
headers: {
'Content-Type': 'application/json',
Authorization: token ? `Bearer ${token}` : '',
},
});
}

/**
* Comprehensive Jest test suite for POST /api/v1/runs/{runId}/replay
*
* This covers:
* 1. Input Validation
* 2. Response Validation
* 3. Response Headers Validation
* 4. Edge Case & Limit Testing
* 5. Testing Authorization & Authentication
*/
describe('POST /api/v1/runs/{runId}/replay', () => {
// A known valid runId for testing (replace with a real one if available).
// In a real-world setting, you might first create a run, then replay it.
const validRunId = 'existing-run-id';

// A runId that is presumably not found in the system.
const nonExistentRunId = 'non-existent-run-id';

// A runId that is invalid (e.g., empty), expected to cause error 400 or 422.
const invalidRunId = '';

// A runId that is malformed or extremely large.
const largeRunId = 'x'.repeat(1024); // 1024 characters

it('should replay a run successfully with a valid runId (expect 200)', async () => {
const axiosInstance = createAxiosInstance(AUTH_TOKEN);
const response: AxiosResponse = await axiosInstance.post(
`/api/v1/runs/${validRunId}/replay`
);

// Check status code
expect(response.status).toBe(200);

// Check response headers
expect(response.headers['content-type']).toMatch(/application\/json/i);

// Check response body schema
expect(response.data).toHaveProperty('id');
expect(typeof response.data.id).toBe('string');
});

it('should return 400 or 422 for an invalid or empty runId', async () => {
const axiosInstance = createAxiosInstance(AUTH_TOKEN);
const response: AxiosResponse = await axiosInstance.post(
`/api/v1/runs/${invalidRunId}/replay`
);

// Expect 400 or 422
expect([400, 422]).toContain(response.status);

// Check response headers
expect(response.headers['content-type']).toMatch(/application\/json/i);

// Check error structure
expect(response.data).toHaveProperty('error');
// The error might be one of:
// - "Invalid or missing run ID"
// - "Failed to create new run"
// or another validation message if 422 is used.
});

it('should return 404 if the runId does not exist', async () => {
const axiosInstance = createAxiosInstance(AUTH_TOKEN);
const response: AxiosResponse = await axiosInstance.post(
`/api/v1/runs/${nonExistentRunId}/replay`
);

// Expect 404
expect(response.status).toBe(404);

// Check response headers
expect(response.headers['content-type']).toMatch(/application\/json/i);

expect(response.data).toHaveProperty('error');
// Should be "Run not found" as per schema
expect(response.data.error).toBe('Run not found');
});

it('should handle extremely large runId (expect 400 or 422)', async () => {
const axiosInstance = createAxiosInstance(AUTH_TOKEN);
const response: AxiosResponse = await axiosInstance.post(
`/api/v1/runs/${largeRunId}/replay`
);

// We expect the server to reject this with 400 or 422.
expect([400, 422]).toContain(response.status);

expect(response.headers['content-type']).toMatch(/application\/json/i);
expect(response.data).toHaveProperty('error');
});

it('should return 401 or 403 when no auth token is provided', async () => {
const axiosInstance = createAxiosInstance(); // No token
const response: AxiosResponse = await axiosInstance.post(
`/api/v1/runs/${validRunId}/replay`
);

// Expect 401 or 403 for missing or invalid token
expect([401, 403]).toContain(response.status);

// Check response headers
expect(response.headers['content-type']).toMatch(/application\/json/i);

// Check error structure
expect(response.data).toHaveProperty('error');
// The error might be "Invalid or Missing API key" or another unauthorized/forbidden error.
});

it('should return 401 or 403 for an invalid auth token', async () => {
const axiosInstance = createAxiosInstance('invalid-token');
const response: AxiosResponse = await axiosInstance.post(
`/api/v1/runs/${validRunId}/replay`
);

expect([401, 403]).toContain(response.status);
expect(response.headers['content-type']).toMatch(/application\/json/i);
expect(response.data).toHaveProperty('error');
});

it('should handle potential server error (5xx) gracefully', async () => {
// Forcing a 500 can be tricky, but we can show a test skeleton.
// In practice, you might set up a scenario that triggers a server error.

const axiosInstance = createAxiosInstance(AUTH_TOKEN);

// This is just a demonstration. Adjust if you have a known condition that triggers 500.
// For example, you might pass some parameter that the server is known to handle incorrectly.
const response: AxiosResponse = await axiosInstance.post(
`/api/v1/runs/${validRunId}/replay`,
{
// Possibly a known invalid or conflicting payload if the API expects or allows a body.
}
);

// If the server truly returns 500, you can test it like:
if (response.status >= 500 && response.status < 600) {
expect(response.status).toBeGreaterThanOrEqual(500);
expect(response.headers['content-type']).toMatch(/application\/json/i);
expect(response.data).toHaveProperty('error');
} else {
// If no 5xx is returned, at least check that we did not succeed.
expect(response.status).not.toBe(200);
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import axios, { AxiosInstance, AxiosResponse } from "axios";
import { describe, it, beforeAll, expect } from "@jest/globals";

/**
* Test file for POST /api/v1/runs/{runId}/reschedule endpoint
* Framework: Jest
* Language: TypeScript
* HTTP Client: Axios
*
* Make sure to set the following environment variables:
* - API_BASE_URL (e.g., https://api.example.com)
* - API_AUTH_TOKEN (your valid API token)
*/

const baseURL = process.env.API_BASE_URL || "http://localhost:3000";
const validToken = process.env.API_AUTH_TOKEN || "";

// Helper function to create an Axios instance with or without auth
function createAxiosInstance(useAuth = true): AxiosInstance {
const headers: Record<string, string> = {};
if (useAuth) {
headers["Authorization"] = `Bearer ${validToken}`;
}

return axios.create({
baseURL,
headers,
});
}

// A valid run ID for successful testing (assumes a run in the DELAYED state).
// Update "validRunId" and "delayedRunId" with appropriate test values.
const validRunId = "123";
// Invalid run IDs to test error handling
const invalidRunId = "abc";
const nonExistentRunId = "9999999"; // ID that presumably does not exist

// Sample body that might match the expected request schema.
// Adjust field names/types based on actual OpenAPI schema.
interface RescheduleRunRequest {
// Example field: the new delay (in seconds) for the delayed run
delayInSeconds: number;
}

const validRequestBody: RescheduleRunRequest = {
delayInSeconds: 300, // 5 minutes
};

// Some variants for edge cases
const zeroDelayRequestBody: RescheduleRunRequest = {
delayInSeconds: 0,
};

const largeDelayRequestBody: RescheduleRunRequest = {
delayInSeconds: 999999999, // Arbitrarily large number
};

const invalidRequestBodyType: any = {
delayInSeconds: "not-a-number", // Wrong data type
};

// Utility to check common headers
function expectCommonHeaders(response: AxiosResponse) {
// Content-Type should be application/json on success or error
expect(response.headers["content-type"]).toMatch(/application\/json/i);
// You can add more header checks here, e.g., Cache-Control
// expect(response.headers["cache-control"]).toBeDefined();
}

describe("POST /api/v1/runs/{runId}/reschedule", () => {
let client: AxiosInstance;

beforeAll(() => {
client = createAxiosInstance();
});

/**
* 1. Input Validation Tests
*/
describe("Input Validation", () => {
it("should return 400 or 422 when runId is invalid", async () => {
expect.assertions(2);
try {
await client.post(`/api/v1/runs/${invalidRunId}/reschedule`, validRequestBody);
} catch (error: any) {
expect([400, 422]).toContain(error.response.status);
expectCommonHeaders(error.response);
}
});

it("should return 400 or 422 when request body has invalid data type", async () => {
expect.assertions(2);
try {
await client.post(`/api/v1/runs/${validRunId}/reschedule`, invalidRequestBodyType);
} catch (error: any) {
expect([400, 422]).toContain(error.response.status);
expectCommonHeaders(error.response);
}
});

it("should return 400 or 422 when required body is missing", async () => {
expect.assertions(2);
try {
// Sending undefined or empty body
await client.post(`/api/v1/runs/${validRunId}/reschedule`, {});
} catch (error: any) {
expect([400, 422]).toContain(error.response.status);
expectCommonHeaders(error.response);
}
});
});

/**
* 2. Response Validation Tests
*/
describe("Response Validation", () => {
it("should return 200 and match the schema on valid input", async () => {
const response = await client.post<unknown>(
`/api/v1/runs/${validRunId}/reschedule`,
validRequestBody
);

expect(response.status).toBe(200);
expectCommonHeaders(response);

// Example schema checks (the actual schema depends on RetrieveRunResponse)
// For demonstration, we assume it might have an 'id' (string) and 'status' (string).
// Adjust field checks to match your actual schema.
const data = response.data as {
id?: string;
status?: string;
[key: string]: unknown;
};

expect(data).toBeDefined();
expect(typeof data.id).toBe("string");
expect(typeof data.status).toBe("string");
});

it("should return 404 if run does not exist", async () => {
expect.assertions(2);
try {
await client.post(`/api/v1/runs/${nonExistentRunId}/reschedule`, validRequestBody);
} catch (error: any) {
expect(error.response.status).toBe(404);
expectCommonHeaders(error.response);
}
});
});

/**
* 3. Response Headers Validation
*/
describe("Response Headers Validation", () => {
it("should include correct headers on success", async () => {
const response = await client.post<unknown>(
`/api/v1/runs/${validRunId}/reschedule`,
validRequestBody
);

expect(response.status).toBe(200);
expect(response.headers["content-type"]).toMatch(/application\/json/i);
});
});

/**
* 4. Edge Case & Limit Testing
*/
describe("Edge Case & Limit Testing", () => {
it("should handle zero delay gracefully", async () => {
// 0 might be a boundary value for the delay
const response = await client.post<unknown>(
`/api/v1/runs/${validRunId}/reschedule`,
zeroDelayRequestBody
);
expect(response.status).toBe(200);
expectCommonHeaders(response);
});

it("should handle large delay values", async () => {
const response = await client.post<unknown>(
`/api/v1/runs/${validRunId}/reschedule`,
largeDelayRequestBody
);
expect(response.status).toBe(200);
expectCommonHeaders(response);
});

it("should return 401 or 403 if request is unauthorized", async () => {
expect.assertions(2);
try {
const unauthClient = createAxiosInstance(false);
await unauthClient.post(`/api/v1/runs/${validRunId}/reschedule`, validRequestBody);
} catch (error: any) {
expect([401, 403]).toContain(error.response.status);
expectCommonHeaders(error.response);
}
});

it("should handle server errors (simulated test)", async () => {
/**
* This is a placeholder example. If you have a way to trigger 500 errors (or other 5xx errors),
* you can do so here. Otherwise, you might mock or simulate it.
*/
expect(true).toBe(true);
});
});

/**
* 5. Testing Authorization & Authentication
* Covered in the unauthorized test above. Additional tests can be added
* if there are multiple roles or permission levels.
*/
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import axios, { AxiosError } from 'axios';

describe('POST /api/v1/schedules/{schedule_id}/activate', () => {
let baseUrl: string;
let token: string;
// Replace with real IDs if available/testable in your environment.
let validScheduleId = 'valid_schedule_id';
let nonImperativeScheduleId = 'non_imperative_schedule_id';
let nonExistentScheduleId = 'non_existent_schedule_id';
let largeScheduleId = 'x'.repeat(1000); // 1000 characters

beforeAll(() => {
// Load env vars for base URL and auth token.
baseUrl = process.env.API_BASE_URL || '';
token = process.env.API_AUTH_TOKEN || '';
});

const getHeaders = (authToken: string | null = token) => {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
if (authToken) {
headers['Authorization'] = `Bearer ${authToken}`;
}
return headers;
};

it('should activate the schedule with valid ID and return 200', async () => {
expect(baseUrl).toBeTruthy();
expect(token).toBeTruthy();

const url = `${baseUrl}/api/v1/schedules/${validScheduleId}/activate`;

const response = await axios.post(url, {}, {
headers: getHeaders()
});

// Response Validation
expect(response.status).toBe(200);
expect(response.headers['content-type']).toContain('application/json');

// Optionally validate other headers
// expect(response.headers['cache-control']).toBeDefined();
// expect(response.headers['x-ratelimit']).toBeDefined();

// Response body schema validation (partial example)
expect(response.data).toHaveProperty('id');
expect(response.data).toHaveProperty('status');
// Add further field/type checks as needed.
});

it('should return 401 or 403 when token is invalid', async () => {
expect(baseUrl).toBeTruthy();

const url = `${baseUrl}/api/v1/schedules/${validScheduleId}/activate`;

try {
await axios.post(url, {}, {
headers: getHeaders('invalid_token')
});
fail('Request should have failed with 401 or 403');
} catch (err) {
const error = err as AxiosError;
if (error.response) {
expect([401, 403]).toContain(error.response.status);
} else {
throw error;
}
}
});

it('should return 401 or 403 when token is missing', async () => {
expect(baseUrl).toBeTruthy();

const url = `${baseUrl}/api/v1/schedules/${validScheduleId}/activate`;

try {
await axios.post(url, {}, {
headers: getHeaders(null)
});
fail('Request should have failed with 401 or 403');
} catch (err) {
const error = err as AxiosError;
if (error.response) {
expect([401, 403]).toContain(error.response.status);
} else {
throw error;
}
}
});

it('should return 400 or 422 if schedule_id is empty', async () => {
expect(baseUrl).toBeTruthy();
expect(token).toBeTruthy();

// Intentionally leave schedule_id empty.
const url = `${baseUrl}/api/v1/schedules//activate`;

try {
await axios.post(url, {}, {
headers: getHeaders()
});
fail('Request should have failed with 400 or 422');
} catch (err) {
const error = err as AxiosError;
if (error.response) {
expect([400, 422]).toContain(error.response.status);
} else {
throw error;
}
}
});

it('should return 400 or 422 if schedule_id is extremely large', async () => {
expect(baseUrl).toBeTruthy();
expect(token).toBeTruthy();

const url = `${baseUrl}/api/v1/schedules/${largeScheduleId}/activate`;

try {
await axios.post(url, {}, {
headers: getHeaders()
});
fail('Request should have failed with 400 or 422');
} catch (err) {
const error = err as AxiosError;
if (error.response) {
expect([400, 422]).toContain(error.response.status);
} else {
throw error;
}
}
});

it('should return 404 if the schedule_id does not exist', async () => {
expect(baseUrl).toBeTruthy();
expect(token).toBeTruthy();

const url = `${baseUrl}/api/v1/schedules/${nonExistentScheduleId}/activate`;

try {
await axios.post(url, {}, {
headers: getHeaders()
});
fail('Request should have failed with 404');
} catch (err) {
const error = err as AxiosError;
if (error.response) {
expect(error.response.status).toBe(404);
} else {
throw error;
}
}
});

it('should return 400 or 422 if the schedule is not IMPERATIVE', async () => {
expect(baseUrl).toBeTruthy();
expect(token).toBeTruthy();

const url = `${baseUrl}/api/v1/schedules/${nonImperativeScheduleId}/activate`;

try {
await axios.post(url, {}, {
headers: getHeaders()
});
fail('Request should have failed with 400 or 422');
} catch (err) {
const error = err as AxiosError;
if (error.response) {
expect([400, 422]).toContain(error.response.status);
} else {
throw error;
}
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import axios from 'axios';
import { describe, it, expect, beforeAll } from '@jest/globals';

/**
* Jest test suite for the POST /api/v1/schedules/:schedule_id/deactivate endpoint.
* This suite covers:
* 1. Input Validation
* 2. Response Validation
* 3. Response Headers Validation
* 4. Edge Case & Limit Testing
* 5. Authorization & Authentication Testing
*
* Prerequisites:
* - Set environment variables API_BASE_URL and API_AUTH_TOKEN.
* - The API might return 400 or 422 for invalid inputs.
* - The API might return 401 or 403 for unauthorized or forbidden requests.
*/

describe('POST /api/v1/schedules/:schedule_id/deactivate', () => {
let baseUrl: string;
let token: string;

beforeAll(() => {
baseUrl = process.env.API_BASE_URL || '';
token = process.env.API_AUTH_TOKEN || '';
});

it('should deactivate a schedule successfully with a valid schedule_id (expect 200)', async () => {
// Replace with a known-valid schedule ID
const validScheduleId = 'someExistingImperativeScheduleId';
let response: any;

try {
response = await axios.post(
`${baseUrl}/api/v1/schedules/${validScheduleId}/deactivate`,
null,
{
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token
}
}
);
} catch (error: any) {
// If we catch an error, the test fails
expect(error).toBeFalsy();
}

expect(response).toBeDefined();
expect(response.status).toBe(200);

// Basic response body validation
expect(response.data).toBeDefined();
expect(response.data).toHaveProperty('id');
expect(response.data).toHaveProperty('name');
expect(response.data).toHaveProperty('status');

// Response header validation
expect(response.headers['content-type']).toContain('application/json');
});

it('should return 401 or 403 for an invalid or missing auth token', async () => {
// Replace with a known-valid schedule ID
const validScheduleId = 'someExistingImperativeScheduleId';
let error: any;

try {
// Provide an invalid token
await axios.post(
`${baseUrl}/api/v1/schedules/${validScheduleId}/deactivate`,
null,
{
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer invalid_or_missing_token'
}
}
);
} catch (err: any) {
error = err;
}

expect(error).toBeDefined();
// Could be 401 or 403
expect([401, 403]).toContain(error?.response?.status);
});

it('should return 404 if the schedule is not found', async () => {
const notFoundScheduleId = 'thisScheduleDoesNotExist';
let error: any;

try {
await axios.post(
`${baseUrl}/api/v1/schedules/${notFoundScheduleId}/deactivate`,
null,
{
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token
}
}
);
} catch (err: any) {
error = err;
}

expect(error).toBeDefined();
expect(error.response?.status).toBe(404);
});

it('should return 400 or 422 when schedule_id is invalid (e.g. empty string)', async () => {
// Using whitespace or empty string to simulate invalid input
const invalidScheduleId = ' ';
let error: any;

try {
await axios.post(
`${baseUrl}/api/v1/schedules/${invalidScheduleId}/deactivate`,
null,
{
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token
}
}
);
} catch (err: any) {
error = err;
}

expect(error).toBeDefined();
// Could be 400 or 422
expect([400, 422]).toContain(error?.response?.status);
});

it('should handle a large schedule_id (likely 400, 422, or 404)', async () => {
// Very long fake schedule_id
const largeScheduleId = 'a'.repeat(256);
let error: any;

try {
await axios.post(
`${baseUrl}/api/v1/schedules/${largeScheduleId}/deactivate`,
null,
{
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token
}
}
);
} catch (err: any) {
error = err;
}

expect(error).toBeDefined();
// Could be 400, 422, or 404 depending on server-side validation
expect([400, 422, 404]).toContain(error?.response?.status);
});

it('should return 401 or 403 if the user is not authenticated at all', async () => {
// Attempt request without any Authorization header
const validScheduleId = 'someExistingImperativeScheduleId';
let error: any;

try {
await axios.post(
`${baseUrl}/api/v1/schedules/${validScheduleId}/deactivate`,
null,
{
headers: {
'Content-Type': 'application/json'
}
}
);
} catch (err: any) {
error = err;
}

expect(error).toBeDefined();
// Could be 401 or 403
expect([401, 403]).toContain(error?.response?.status);
});
});
148 changes: 148 additions & 0 deletions chapter_api_tests/2024-04/validation/test_post_api-v1-schedules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import axios, { AxiosResponse } from 'axios';
import { describe, it, expect } from '@jest/globals';

/**
* Jest test suite for POST /api/v1/schedules.
* This suite covers:
* 1. Input Validation (required params, data types, edge cases)
* 2. Response Validation (status codes, schema, error handling)
* 3. Response Headers Validation (Content-Type, etc.)
* 4. Edge Case & Limit Testing (large payload, boundary values, invalid requests)
* 5. Testing Authorization & Authentication
*/

describe('POST /api/v1/schedules', () => {
const baseURL = process.env.API_BASE_URL;
const authToken = process.env.API_AUTH_TOKEN;

// Helper function to build the request config (including Authorization header)
const buildConfig = (token?: string) => {
return {
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
};
};

// A valid payload conforming to the (hypothetical) required schema for creating an IMPERATIVE schedule.
// Adjust the fields to match your actual ScheduleObject schema.
const validPayload = {
name: 'My IMPERATIVE Schedule',
type: 'IMPERATIVE',
startDate: '2023-12-31T00:00:00Z',
endDate: '2024-01-07T00:00:00Z',
repeat: false,
};

// Utility for checking expected properties in the response body.
// Adjust to match your actual schema structure.
const validateScheduleObject = (data: any) => {
// Example checks based on hypothetical "ScheduleObject" schema:
expect(data).toHaveProperty('id');
expect(typeof data.id).toBe('string');
expect(data).toHaveProperty('name');
expect(typeof data.name).toBe('string');
expect(data).toHaveProperty('type');
expect(data.type).toBe('IMPERATIVE');
};

it('should create a new schedule with valid payload (200)', async () => {
expect(baseURL).toBeDefined();
expect(authToken).toBeDefined();

const url = `${baseURL}/api/v1/schedules`;

const response: AxiosResponse = await axios.post(url, validPayload, buildConfig(authToken));

// Response validation
expect(response.status).toBe(200);
expect(response.headers['content-type']).toContain('application/json');

// Validate the response body against the expected schema
validateScheduleObject(response.data);
});

it('should return 400 or 422 if required fields are missing', async () => {
const url = `${baseURL}/api/v1/schedules`;
// Remove a required field (e.g., "type") from the payload
const invalidPayload = { ...validPayload };
delete invalidPayload.type;

try {
await axios.post(url, invalidPayload, buildConfig(authToken));
// If we reach here, no error was thrown, which is unexpected
throw new Error('Expected request to fail with 400 or 422, but it succeeded.');
} catch (error: any) {
expect(error.response).toBeDefined();
expect([400, 422]).toContain(error.response.status);
}
});

it('should return 400 or 422 if an invalid data type is provided', async () => {
const url = `${baseURL}/api/v1/schedules`;
// Provide an invalid "name" type (number instead of string)
const invalidPayload = { ...validPayload, name: 12345 };

try {
await axios.post(url, invalidPayload, buildConfig(authToken));
throw new Error('Expected request to fail with 400 or 422, but it succeeded.');
} catch (error: any) {
expect(error.response).toBeDefined();
expect([400, 422]).toContain(error.response.status);
}
});

it('should return 401 or 403 if the request is unauthorized', async () => {
const url = `${baseURL}/api/v1/schedules`;

try {
// No auth token provided
await axios.post(url, validPayload, buildConfig());
throw new Error('Expected 401 or 403 for unauthorized request, but it succeeded.');
} catch (error: any) {
expect(error.response).toBeDefined();
expect([401, 403]).toContain(error.response.status);
}
});

it('should handle large payload or boundary cases gracefully (400 or 422)', async () => {
const url = `${baseURL}/api/v1/schedules`;
// Construct an extremely long string for name
const largeString = 'a'.repeat(2000); // Example boundary test
const boundaryPayload = { ...validPayload, name: largeString };

try {
await axios.post(url, boundaryPayload, buildConfig(authToken));
// Depending on API constraints, this may succeed or fail.
// If your schema disallows long strings, expect an error.
// Failing here simply ensures we handle whichever the spec dictates.
} catch (error: any) {
// If it fails, 400 or 422 is acceptable.
if (error.response) {
expect([400, 422]).toContain(error.response.status);
} else {
// If no response, it might be a server/network error.
throw error;
}
}
});

it('should return appropriate error code when sending empty request body (400 or 422)', async () => {
const url = `${baseURL}/api/v1/schedules`;

try {
await axios.post(url, {}, buildConfig(authToken));
throw new Error('Expected 400 or 422 for empty request body, but it succeeded.');
} catch (error: any) {
expect(error.response).toBeDefined();
expect([400, 422]).toContain(error.response.status);
}
});

// Additional tests can be added for:
// - server errors (5xx) handling
// - rate limiting scenarios
// - forbidden (403) with valid token but insufficient permissions (if applicable)
// - etc.
});
130 changes: 130 additions & 0 deletions chapter_api_tests/2024-04/validation/test_post_api-v1-tasks-batch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import axios, { AxiosResponse, AxiosError } from 'axios';

const API_BASE_URL = process.env.API_BASE_URL;
const API_AUTH_TOKEN = process.env.API_AUTH_TOKEN;

describe('POST /api/v1/tasks/batch', () => {
/**
* Helper function to create a valid tasks payload.
* @param count - Number of tasks to generate.
*/
function createValidPayload(count = 1) {
const tasks = [];
for (let i = 0; i < count; i++) {
tasks.push({
taskId: `task-${i}`,
data: {
foo: `bar-${i}`
}
});
}
return { tasks };
}

/**
* Create a client instance with authentication.
* We set validateStatus to always return true,
* so we can handle the status codes directly in the tests.
*/
const client = axios.create({
baseURL: API_BASE_URL,
headers: {
Authorization: `Bearer ${API_AUTH_TOKEN}`
},
validateStatus: () => true
});

it('should return 401 or 403 if auth token is missing', async () => {
// No Authorization header here
const response = await axios.post(`${API_BASE_URL}/api/v1/tasks/batch`, createValidPayload(), {
validateStatus: () => true
});

// Expecting 401 Unauthorized or 403 Forbidden
expect([401, 403]).toContain(response.status);
});

it('should return 200 for a valid payload', async () => {
const response = await client.post('/api/v1/tasks/batch', createValidPayload());

// Expecting successful response
expect(response.status).toBe(200);

// Validate the response headers
expect(response.headers['content-type']).toMatch(/application\/json/i);

// Validate the response body structure (basic check)
expect(response.data).toBeDefined();
// Additional checks can be performed here, e.g.:
// expect(response.data).toHaveProperty('results');
});

it('should return 400 or 422 for an empty payload', async () => {
const response = await client.post('/api/v1/tasks/batch', {});

// Expecting 400 Bad Request or 422 Unprocessable Entity
expect([400, 422]).toContain(response.status);
});

it('should return 400 or 422 when tasks exceed 500', async () => {
// Create a payload with 501 tasks
const response = await client.post('/api/v1/tasks/batch', createValidPayload(501));

// Expecting 400 Bad Request or 422 Unprocessable Entity
expect([400, 422]).toContain(response.status);
});

it('should return 400 or 422 for invalid data type in payload', async () => {
// Here the tasks property is a string instead of an array
const invalidPayload = {
tasks: 'invalid'
};

const response = await client.post('/api/v1/tasks/batch', invalidPayload);

// Expecting 400 Bad Request or 422 Unprocessable Entity
expect([400, 422]).toContain(response.status);
});

it('should return 404 for non-existing endpoint', async () => {
// Hitting a non-existing path to check for 404
const response = await client.post('/api/v1/nonexisting', createValidPayload());
expect(response.status).toBe(404);
});

it('should return 401 or 403 if auth token is invalid', async () => {
// Create a client with an invalid token
const clientWithInvalidToken = axios.create({
baseURL: API_BASE_URL,
headers: {
Authorization: 'Bearer invalid_token'
},
validateStatus: () => true
});

const response = await clientWithInvalidToken.post('/api/v1/tasks/batch', createValidPayload());

// Expecting 401 Unauthorized or 403 Forbidden
expect([401, 403]).toContain(response.status);
});

it('should handle an empty tasks array (0 tasks)', async () => {
// Depending on the API's design, 0 tasks might be valid or invalid
const payload = { tasks: [] };
const response = await client.post('/api/v1/tasks/batch', payload);

// The status could be 200 (if empty is valid) or 400/422 if not
expect([200, 400, 422]).toContain(response.status);
});

it('should include the correct response headers for valid requests', async () => {
const response = await client.post('/api/v1/tasks/batch', createValidPayload());

// Check the Content-Type header
expect(response.headers['content-type']).toMatch(/application\/json/i);

// Additional header checks can go here
// For example:
// expect(response.headers).toHaveProperty('cache-control');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import axios, { AxiosError } from 'axios';
import { describe, test, expect, beforeAll } from '@jest/globals';

const baseURL = process.env.API_BASE_URL || '';
const validAuthToken = process.env.API_AUTH_TOKEN || '';

function createAxiosInstance(token?: string) {
return axios.create({
baseURL,
headers: {
Authorization: token ? `Bearer ${token}` : '',
'Content-Type': 'application/json',
},
});
}

describe('POST /api/v1/tasks/{taskIdentifier}/trigger', () => {
let axiosInstance = createAxiosInstance(validAuthToken);

beforeAll(() => {
// Recreate axios instance if needed, for example ensuring fresh tokens
axiosInstance = createAxiosInstance(validAuthToken);
});

test('should trigger a task successfully with valid data (200)', async () => {
const taskIdentifier = 'validTask123';

const response = await axiosInstance.post(`/api/v1/tasks/${taskIdentifier}/trigger`, {});

expect(response.status).toBe(200);
expect(response.headers['content-type']).toMatch(/application\/json/i);
// Example response schema validation
expect(response.data).toHaveProperty('status');
expect(typeof response.data.status).toBe('string');
});

test('should return 401 or 403 for unauthorized requests when token is missing or invalid', async () => {
const noAuthInstance = createAxiosInstance();
const taskIdentifier = 'validTask123';

try {
await noAuthInstance.post(`/api/v1/tasks/${taskIdentifier}/trigger`, {});
fail('Request should not succeed without a valid token');
} catch (error) {
if (error instanceof AxiosError && error.response) {
// API might respond with 401 or 403 if unauthorized
expect([401, 403]).toContain(error.response.status);
expect(error.response.headers['content-type']).toMatch(/application\/json/i);
} else {
throw error;
}
}
});

test('should return 400 or 422 for invalid or malformed request data', async () => {
// For example, invalid or empty taskIdentifier
const invalidTaskIdentifier = '';

try {
await axiosInstance.post(`/api/v1/tasks/${invalidTaskIdentifier}/trigger`, { foo: 'bar' });
fail('Request should not succeed with an invalid path parameter');
} catch (error) {
if (error instanceof AxiosError && error.response) {
// Depending on implementation, API might return 400 or 422
expect([400, 422]).toContain(error.response.status);
expect(error.response.headers['content-type']).toMatch(/application\/json/i);
// Validate error response structure
expect(error.response.data).toHaveProperty('error');
expect(typeof error.response.data.error).toBe('string');
} else {
throw error;
}
}
});

test('should return 404 if the task identifier does not exist', async () => {
const nonExistentTaskIdentifier = 'does-not-exist-000';

try {
await axiosInstance.post(`/api/v1/tasks/${nonExistentTaskIdentifier}/trigger`, {});
fail('Request should not succeed for a non-existent resource');
} catch (error) {
if (error instanceof AxiosError && error.response) {
expect(error.response.status).toBe(404);
expect(error.response.headers['content-type']).toMatch(/application\/json/i);
} else {
throw error;
}
}
});

test('should handle large payload gracefully', async () => {
const taskIdentifier = 'validTask123';
// Simulate a large request body
const largeData = 'x'.repeat(1000000);

const response = await axiosInstance.post(`/api/v1/tasks/${taskIdentifier}/trigger`, { testData: largeData });
// Depending on API limits, we might get 200, 400, or 413
expect([200, 400, 413]).toContain(response.status);
});

test('should include correct response headers when successful', async () => {
const taskIdentifier = 'validTask123';
const response = await axiosInstance.post(`/api/v1/tasks/${taskIdentifier}/trigger`, {});

expect(response.headers).toHaveProperty('content-type');
expect(response.headers['content-type']).toMatch(/application\/json/i);
});

test('should handle server errors (5xx) gracefully (simulated)', async () => {
// This test assumes there is a way to simulate a server error by using a special identifier
try {
await axiosInstance.post('/api/v1/tasks/errorTrigger/trigger', {});
fail('Request should have triggered an error');
} catch (error) {
if (error instanceof AxiosError && error.response) {
// We expect certain 5xx codes here
expect([500, 503]).toContain(error.response.status);
} else {
throw error;
}
}
});
});
Loading