Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,5 @@ To setup and develop locally or contribute to the open source project, follow ou
<a href="https://github.com/triggerdotdev/trigger.dev/graphs/contributors">
<img src="https://contrib.rocks/image?repo=triggerdotdev/trigger.dev" />
</a>

## Test
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import axios, { AxiosInstance } from 'axios';
import { describe, it, expect, beforeAll } from '@jest/globals';

/**
* DELETE /api/v1/projects/{projectRef}/envvars/{env}/{name}
* Summary: Delete environment variable
* Description: Delete a specific environment variable for a specific project and environment.
*
* This test suite covers:
* 1. Input Validation (e.g., missing or invalid path params)
* 2. Response Validation (e.g., correct status codes and JSON schema checks)
* 3. Response Headers Validation
* 4. Edge Case & Limit Testing (e.g., extremely long params)
* 5. Authorization & Authentication Tests
*/

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

// Define some valid test data
const validProjectRef = 'exampleProjectRef';
const validEnv = 'staging';
const validName = 'EXAMPLE_VAR';

beforeAll(() => {
// Create an Axios instance for all requests in this suite
// Loads base URL and auth token from environment variables
request = axios.create({
baseURL: process.env.API_BASE_URL,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.API_AUTH_TOKEN}`,
},
validateStatus: () => true, // we'll handle status codes manually in each test
});
});

/**
* 1. Valid Request
*/
describe('Valid Request', () => {
it('should delete the environment variable successfully (expect 200)', async () => {
// Perform the DELETE request with valid path parameters
const response = await request.delete(
`/api/v1/projects/${validProjectRef}/envvars/${validEnv}/${validName}`
);

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

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

// Validate response body structure (SucceedResponse)
// Example schema might have { message: 'Environment variable deleted successfully' }
expect(response.data).toHaveProperty('message');
expect(typeof response.data.message).toBe('string');
});
});

/**
* 2. Input Validation (Invalid or Malformed Parameters)
*/
describe('Input Validation', () => {
it('should return 400 or 422 when path param is empty or malformed', async () => {
// Using an empty name to force an invalid path param scenario
const invalidName = '';

const response = await request.delete(
`/api/v1/projects/${validProjectRef}/envvars/${validEnv}/${invalidName}`
);

// The API could return either 400 or 422 for invalid parameters
expect([400, 422]).toContain(response.status);

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

// Validate error response body (ErrorResponse)
// Example schema might have { error: 'some error message' }
expect(response.data).toHaveProperty('error');
expect(typeof response.data.error).toBe('string');
});

it('should return 400 or 422 when path param is extremely long', async () => {
// Large string to test boundary condition
const longName = 'A'.repeat(1000);

const response = await request.delete(
`/api/v1/projects/${validProjectRef}/envvars/${validEnv}/${longName}`
);

// The API could return either 400 or 422 for invalid parameters
expect([400, 422]).toContain(response.status);

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

// Validate error response body (ErrorResponse)
expect(response.data).toHaveProperty('error');
expect(typeof response.data.error).toBe('string');
});
});

/**
* 3. Authorization & Authentication
*/
describe('Authorization & Authentication', () => {
it('should return 401 or 403 if the token is missing or invalid', async () => {
// Create another Axios instance without Authorization header
const unauthRequest = axios.create({
baseURL: process.env.API_BASE_URL,
validateStatus: () => true,
});

const response = await unauthRequest.delete(
`/api/v1/projects/${validProjectRef}/envvars/${validEnv}/${validName}`
);

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

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

// Validate error response structure
expect(response.data).toHaveProperty('error');
expect(typeof response.data.error).toBe('string');
});
});

/**
* 4. Resource Not Found
*/
describe('Resource Not Found', () => {
it('should return 404 if the specified environment variable does not exist', async () => {
const nonExistentName = 'NON_EXISTENT_VAR';

const response = await request.delete(
`/api/v1/projects/${validProjectRef}/envvars/${validEnv}/${nonExistentName}`
);

// Expect 404 for missing resource
expect(response.status).toBe(404);

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

// Validate error response body
expect(response.data).toHaveProperty('error');
expect(typeof response.data.error).toBe('string');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import axios, { AxiosInstance } from 'axios';
import { describe, it, expect, beforeAll } from '@jest/globals';

/**
* This test suite validates the DELETE /api/v1/schedules/{schedule_id} endpoint.
* It covers:
* 1. Input Validation
* 2. Response Validation
* 3. Response Headers Validation
* 4. Edge Cases & Limit Testing
* 5. Authorization & Authentication
*
* Requirements:
* - You must have API_BASE_URL and API_AUTH_TOKEN set in your environment variables.
* - The endpoint is DELETE /api/v1/schedules/{schedule_id}
* - A valid schedule_id corresponds to an IMPERATIVE schedule that actually exists.
* - The endpoint may respond with 400 or 422 for invalid input.
* - The endpoint may respond with 401 or 403 for unauthorized or forbidden access.
* - The endpoint may respond with 404 if the resource is not found.
*/

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

beforeAll(() => {
// Load environment variables
baseURL = process.env.API_BASE_URL || '';
authToken = process.env.API_AUTH_TOKEN || '';

// Create an Axios instance
apiClient = axios.create({
baseURL,
headers: {
'Content-Type': 'application/json',
},
validateStatus: () => true, // We'll manually check status codes to handle multiple possible outcomes
});
});

// Utility function to get the Authorization header
function getAuthHeaders(token?: string) {
if (token) {
return { Authorization: `Bearer ${token}` };
}
if (authToken) {
return { Authorization: `Bearer ${authToken}` };
}
return {};
}

/**
* Note on test data:
* - Adjust these IDs to match real data in your test environment.
* - "validScheduleId" should be an existing imperative schedule ID.
* - "nonExistentScheduleId" doesn't exist, triggering a 404.
* - "invalidScheduleId" simulates malformed input.
*/
const validScheduleId = 'validScheduleId';
const nonExistentScheduleId = 'nonExistentScheduleId';
const invalidScheduleId = ''; // Empty string or any invalid type

/******************
* 1) Input Validation
******************/
describe('Input Validation', () => {
it('should return 404 or 400/422 when schedule_id is missing in the path', async () => {
// Some servers might interpret a missing ID as a 404 (route not found)
// Others might throw 400/422 if the path was recognized but the ID was empty.
const response = await apiClient.delete('/api/v1/schedules/', {
headers: getAuthHeaders(),
});
expect([400, 404, 422]).toContain(response.status);
});

it('should return 400 or 422 if schedule_id is invalid (empty string)', async () => {
const response = await apiClient.delete(`/api/v1/schedules/${invalidScheduleId}`, {
headers: getAuthHeaders(),
});
// Depending on implementation, could be 400, 422, or even 404.
expect([400, 404, 422]).toContain(response.status);
});

// If your API expects strictly string type for schedule_id, you can add more tests for numeric or other malformed types.
});

/******************
* 2) Response Validation
******************/
describe('Response Validation', () => {
it('should delete the schedule and return 200 for a valid existing schedule_id', async () => {
// This test assumes the validScheduleId is a real schedule.
// If the schedule is not truly existing, the test may fail or return 404.
// Ensure validScheduleId references an actual IMPERATIVE schedule.
const response = await apiClient.delete(`/api/v1/schedules/${validScheduleId}`, {
headers: getAuthHeaders(),
});

// We expect 200 for a successful deletion.
expect(response.status).toBe(200);
// Optionally, if response has a body, we can validate the schema.
// Example: expect(response.data).toHaveProperty('message', 'Schedule deleted successfully');
});

it('should return 404 if the schedule_id does not exist', async () => {
const response = await apiClient.delete(`/api/v1/schedules/${nonExistentScheduleId}`, {
headers: getAuthHeaders(),
});

expect(response.status).toBe(404);
// Optionally check response body if the API returns an error message.
// Example: expect(response.data).toHaveProperty('error', 'Resource not found');
});
});

/******************
* 3) Response Headers Validation
******************/
describe('Response Headers Validation', () => {
it('should return application/json in Content-Type header under normal conditions', async () => {
const response = await apiClient.delete(`/api/v1/schedules/${validScheduleId}`, {
headers: getAuthHeaders(),
});

// Some APIs only return a body for certain status codes. If there's no body, it can differ.
// But typically for a JSON-based API, we expect "application/json".
const contentType = response.headers['content-type'] || '';
expect(contentType).toContain('application/json');
});
});

/******************
* 4) Edge Cases & Limit Testing
******************/
describe('Edge Cases & Limit Testing', () => {
it('should handle unauthorized requests (missing token) with 401 or 403', async () => {
// Calling without Authorization header
const response = await apiClient.delete(`/api/v1/schedules/${validScheduleId}`);
expect([401, 403]).toContain(response.status);
});

it('should handle requests with an invalid token with 401 or 403', async () => {
const response = await apiClient.delete(`/api/v1/schedules/${validScheduleId}`, {
headers: getAuthHeaders('invalidToken'),
});
expect([401, 403]).toContain(response.status);
});

it('should handle server errors (5xx) gracefully if the server triggers such an error', async () => {
// This test is hypothetical. The actual occurrence might require a special setup.
// We simply show that we expect 5xx if something goes wrong on the server.
// In practice, you could mock or simulate an internal server error.
// Here we just illustrate how to handle if it occurs.

// Example pseudo-code:
// const response = await apiClient.delete(`/api/v1/schedules/specialCaseIdThatCausesError`, {
// headers: getAuthHeaders(),
// });

// For demonstration, we won't actually throw a server error; just a placeholder.
// If you had a special route that triggers a 500, you could test it.
// expect(response.status).toBe(500);
});
});

/******************
* 5) Testing Authorization & Authentication
******************/
describe('Authorization & Authentication', () => {
it('should delete a schedule successfully when authorized', async () => {
// Re-using validScheduleId, ensuring token is provided.
const response = await apiClient.delete(`/api/v1/schedules/${validScheduleId}`, {
headers: getAuthHeaders(),
});

// 200 is expected if the schedule exists, 404 if it was already deleted.
// Some implementations might return 204 No Content for a successful delete.
expect([200, 404]).toContain(response.status);
});

it('should return 401 or 403 if token is expired or invalid', async () => {
const response = await apiClient.delete(`/api/v1/schedules/${validScheduleId}`, {
headers: getAuthHeaders('expiredOrInvalidToken'),
});

expect([401, 403]).toContain(response.status);
});
});
});
Loading