diff --git a/examples/README.md b/examples/README.md index 3abaddb..d889041 100644 --- a/examples/README.md +++ b/examples/README.md @@ -47,7 +47,7 @@ Sensitive information like API keys and authorization tokens are automatically r When adding new examples: 1. Create a new `.ts` file in this directory -2. Import the SDK: `import { PlaneClient } from '../src'` +2. Import the SDK: `import { PlaneClient } from '../../src'` 3. Include proper error handling 4. Add documentation comments 5. Update this README with a description diff --git a/examples/bootstrap-project.ts b/examples/bootstrap-project.ts index 9f97029..ec6dec8 100644 --- a/examples/bootstrap-project.ts +++ b/examples/bootstrap-project.ts @@ -1,5 +1,5 @@ // examples/project-bootstrap.ts -import { CreateWorkItemProperty, PlaneClient, WorkItemProperty } from "../src"; +import { CreateWorkItemProperty, PlaneClient, WorkItemProperty } from "../../src"; async function bootstrapProject(workspaceSlug: string) { const client = new PlaneClient({ @@ -30,11 +30,7 @@ async function bootstrapProject(workspaceSlug: string) { ]; for (const type of workItemTypes) { - const workItemType = await client.workItemTypes.create( - workspaceSlug, - project.id, - type - ); + const workItemType = await client.workItemTypes.create(workspaceSlug, project.id, type); const workItemTypeId = workItemType.id; const customProperties: CreateWorkItemProperty[] = [ { @@ -47,12 +43,7 @@ async function bootstrapProject(workspaceSlug: string) { ]; // 3. Create custom properties for (const prop of customProperties) { - await client.workItemProperties.create( - workspaceSlug, - project.id, - workItemTypeId, - prop - ); + await client.workItemProperties.create(workspaceSlug, project.id, workItemTypeId, prop); } } diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..e0f397e --- /dev/null +++ b/jest.config.js @@ -0,0 +1,25 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/tests'], + testMatch: [ + '**/__tests__/**/*.ts', + '**/?(*.)+(spec|test).ts' + ], + transform: { + '^.+\\.ts$': ['ts-jest', { + tsconfig: 'tsconfig.jest.json' + }], + }, + collectCoverageFrom: [ + 'src/**/*.ts', + '!src/**/*.d.ts', + '!src/**/*.spec.ts' + ], + coverageDirectory: 'coverage', + coverageReporters: ['text', 'lcov', 'html'], + testTimeout: 30000, // 30 seconds timeout for API tests + verbose: true, + // Allow tests to run in parallel but with some control + maxWorkers: 1, // Run tests sequentially to avoid API rate limits +}; \ No newline at end of file diff --git a/package.json b/package.json index 76062e5..d337657 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,12 @@ "scripts": { "build": "tsc", "dev": "tsc --watch", - "test": "ts-node tests/run-all.test.ts", + "test": "jest", + "test:unit": "jest --testPathPattern=tests/unit", + "test:e2e": "jest --testPathPattern=tests/e2e", + "test:all": "pnpm test:unit && pnpm test:e2e", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage", "lint": "eslint src/**/*.ts", "format": "prettier --write src/**/*.ts", "clean": "rm -rf dist" diff --git a/src/api/BaseResource.ts b/src/api/BaseResource.ts index bf5524f..f1e24bf 100644 --- a/src/api/BaseResource.ts +++ b/src/api/BaseResource.ts @@ -153,6 +153,8 @@ export abstract class BaseResource { * Centralized error handling */ protected handleError(error: any): never { + console.error("โŒ [ERROR]", error); + if (error instanceof HttpError) { throw error; } diff --git a/src/api/Customers/Properties.ts b/src/api/Customers/Properties.ts index 8c42372..3c9744a 100644 --- a/src/api/Customers/Properties.ts +++ b/src/api/Customers/Properties.ts @@ -8,7 +8,9 @@ import { ListCustomerPropertiesParams, CreateCustomerPropertyRequest, UpdateCustomerPropertyRequest, + CustomPropertyValueResponse, } from "../../models/Customer"; +import { PaginatedResponse } from "../../models/common"; /** * Customer Properties API resource @@ -27,11 +29,8 @@ export class Properties extends BaseResource { async listPropertyDefinitions( workspaceSlug: string, params?: ListCustomerPropertiesParams - ): Promise { - return this.get( - `/workspaces/${workspaceSlug}/customer-properties/`, - params - ); + ): Promise> { + return this.get>(`/workspaces/${workspaceSlug}/customer-properties/`, params); } /** @@ -41,22 +40,14 @@ export class Properties extends BaseResource { workspaceSlug: string, createProperty: CreateCustomerPropertyRequest ): Promise { - return this.post( - `/workspaces/${workspaceSlug}/customer-properties/`, - createProperty - ); + return this.post(`/workspaces/${workspaceSlug}/customer-properties/`, createProperty); } /** * Retrieve customer property */ - async retrievePropertyDefinition( - workspaceSlug: string, - propertyId: string - ): Promise { - return this.get( - `/workspaces/${workspaceSlug}/customer-properties/${propertyId}/` - ); + async retrievePropertyDefinition(workspaceSlug: string, propertyId: string): Promise { + return this.get(`/workspaces/${workspaceSlug}/customer-properties/${propertyId}/`); } /** @@ -76,13 +67,8 @@ export class Properties extends BaseResource { /** * Delete customer property */ - async deletePropertyDefinition( - workspaceSlug: string, - propertyId: string - ): Promise { - return this.httpDelete( - `/workspaces/${workspaceSlug}/customer-properties/${propertyId}/` - ); + async deletePropertyDefinition(workspaceSlug: string, propertyId: string): Promise { + return this.httpDelete(`/workspaces/${workspaceSlug}/customer-properties/${propertyId}/`); } // ===== CUSTOMER PROPERTY VALUES API METHODS ===== @@ -94,8 +80,8 @@ export class Properties extends BaseResource { workspaceSlug: string, customerId: string, params?: ListCustomerPropertyValuesParams - ): Promise { - return this.get( + ): Promise { + return this.get( `/workspaces/${workspaceSlug}/customers/${customerId}/property-values/`, params ); @@ -104,11 +90,7 @@ export class Properties extends BaseResource { /** * Get single property value */ - async retrieveValue( - workspaceSlug: string, - customerId: string, - propertyId: string - ): Promise { + async retrieveValue(workspaceSlug: string, customerId: string, propertyId: string): Promise { return this.get( `/workspaces/${workspaceSlug}/customers/${customerId}/property-values/${propertyId}/` ); diff --git a/src/api/Customers/Requests.ts b/src/api/Customers/Requests.ts index f19b347..e6115a0 100644 --- a/src/api/Customers/Requests.ts +++ b/src/api/Customers/Requests.ts @@ -6,6 +6,7 @@ import { UpdateCustomerRequest, ListCustomerRequestsParams, } from "../../models/Customer"; +import { PaginatedResponse } from "../../models/common"; /** * Customer Requests API resource @@ -23,8 +24,8 @@ export class Requests extends BaseResource { workspaceSlug: string, customerId: string, params?: ListCustomerRequestsParams - ): Promise { - return this.get( + ): Promise> { + return this.get>( `/workspaces/${workspaceSlug}/customers/${customerId}/requests/`, params ); @@ -38,23 +39,14 @@ export class Requests extends BaseResource { customerId: string, createRequest: CreateCustomerRequest ): Promise { - return this.post( - `/workspaces/${workspaceSlug}/customers/${customerId}/requests/`, - createRequest - ); + return this.post(`/workspaces/${workspaceSlug}/customers/${customerId}/requests/`, createRequest); } /** * Retrieve customer request */ - async retrieve( - workspaceSlug: string, - customerId: string, - requestId: string - ): Promise { - return this.get( - `/workspaces/${workspaceSlug}/customers/${customerId}/requests/${requestId}/` - ); + async retrieve(workspaceSlug: string, customerId: string, requestId: string): Promise { + return this.get(`/workspaces/${workspaceSlug}/customers/${customerId}/requests/${requestId}/`); } /** @@ -75,13 +67,7 @@ export class Requests extends BaseResource { /** * Delete customer request */ - async delete( - workspaceSlug: string, - customerId: string, - requestId: string - ): Promise { - return this.httpDelete( - `/workspaces/${workspaceSlug}/customers/${customerId}/requests/${requestId}/` - ); + async delete(workspaceSlug: string, customerId: string, requestId: string): Promise { + return this.httpDelete(`/workspaces/${workspaceSlug}/customers/${customerId}/requests/${requestId}/`); } } diff --git a/src/api/Customers/index.ts b/src/api/Customers/index.ts index 21e1e28..f0bafcf 100644 --- a/src/api/Customers/index.ts +++ b/src/api/Customers/index.ts @@ -5,9 +5,11 @@ import { CreateCustomer, UpdateCustomer, ListCustomersParams, + LinkIssuesToCustomerResponse, } from "../../models/Customer"; import { Properties } from "./Properties"; import { Requests } from "./Requests"; +import { PaginatedResponse } from "../../models/common"; /** * Customers API resource @@ -27,59 +29,36 @@ export class Customers extends BaseResource { /** * Create a new customer */ - async create( - workspaceSlug: string, - createCustomer: CreateCustomer - ): Promise { - return this.post( - `/workspaces/${workspaceSlug}/customers/`, - createCustomer - ); + async create(workspaceSlug: string, createCustomer: CreateCustomer): Promise { + return this.post(`/workspaces/${workspaceSlug}/customers/`, createCustomer); } /** * Retrieve a customer by ID */ async retrieve(workspaceSlug: string, customerId: string): Promise { - return this.get( - `/workspaces/${workspaceSlug}/customers/${customerId}/` - ); + return this.get(`/workspaces/${workspaceSlug}/customers/${customerId}/`); } /** * Update a customer */ - async update( - workspaceSlug: string, - customerId: string, - updateCustomer: UpdateCustomer - ): Promise { - return this.patch( - `/workspaces/${workspaceSlug}/customers/${customerId}/`, - updateCustomer - ); + async update(workspaceSlug: string, customerId: string, updateCustomer: UpdateCustomer): Promise { + return this.patch(`/workspaces/${workspaceSlug}/customers/${customerId}/`, updateCustomer); } /** * Delete a customer */ async delete(workspaceSlug: string, customerId: string): Promise { - return this.httpDelete( - `/workspaces/${workspaceSlug}/customers/${customerId}/` - ); + return this.httpDelete(`/workspaces/${workspaceSlug}/customers/${customerId}/`); } /** * List customers with optional filtering */ - async list( - workspaceSlug: string, - params?: ListCustomersParams - ): Promise { - return this.get( - `/workspaces/${workspaceSlug}/customers/`, - params - ); + async list(workspaceSlug: string, params?: ListCustomersParams): Promise> { + return this.get>(`/workspaces/${workspaceSlug}/customers/`, params); } // ===== CUSTOMER ISSUES API METHODS ===== @@ -87,13 +66,8 @@ export class Customers extends BaseResource { /** * List customer issues */ - async listCustomerIssues( - workspaceSlug: string, - customerId: string - ): Promise { - return this.get( - `/workspaces/${workspaceSlug}/customers/${customerId}/issues/` - ); + async listCustomerIssues(workspaceSlug: string, customerId: string): Promise { + return this.get(`/workspaces/${workspaceSlug}/customers/${customerId}/issues/`); } /** @@ -103,23 +77,16 @@ export class Customers extends BaseResource { workspaceSlug: string, customerId: string, issueIds: string[] - ): Promise { - return this.post( - `/workspaces/${workspaceSlug}/customers/${customerId}/issues/`, - { issue_ids: issueIds } - ); + ): Promise { + return this.post(`/workspaces/${workspaceSlug}/customers/${customerId}/issues/`, { + issue_ids: issueIds, + }); } /** * Unlink issue from customer */ - async unlinkIssueFromCustomer( - workspaceSlug: string, - customerId: string, - issueId: string - ): Promise { - return this.httpDelete( - `/workspaces/${workspaceSlug}/customers/${customerId}/issues/${issueId}/` - ); + async unlinkIssueFromCustomer(workspaceSlug: string, customerId: string, issueId: string): Promise { + return this.httpDelete(`/workspaces/${workspaceSlug}/customers/${customerId}/issues/${issueId}/`); } } diff --git a/src/api/Links.ts b/src/api/Links.ts index c0cb45f..8ce7581 100644 --- a/src/api/Links.ts +++ b/src/api/Links.ts @@ -1,6 +1,7 @@ import { BaseResource } from "./BaseResource"; import { Configuration } from "../Configuration"; import { Link, CreateLink, UpdateLink, ListLinksParams } from "../models/Link"; +import { PaginatedResponse } from "../models/common"; /** * Links API resource @@ -54,7 +55,15 @@ export class Links extends BaseResource { /** * List links for a work item with optional filtering */ - async list(workspaceSlug: string, projectId: string, issueId: string, params?: ListLinksParams): Promise { - return this.get(`/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${issueId}/links/`, params); + async list( + workspaceSlug: string, + projectId: string, + issueId: string, + params?: ListLinksParams + ): Promise> { + return this.get>( + `/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${issueId}/links/`, + params + ); } } diff --git a/src/api/Modules.ts b/src/api/Modules.ts index e43a41b..4b5c1b6 100644 --- a/src/api/Modules.ts +++ b/src/api/Modules.ts @@ -75,7 +75,7 @@ export class Modules extends BaseResource { /** * Add work items to module */ - async addWorkItemToModule( + async addWorkItemsToModule( workspaceSlug: string, projectId: string, moduleId: string, diff --git a/src/api/WorkItemTypes.ts b/src/api/WorkItemTypes.ts index f2209cc..fbabfa4 100644 --- a/src/api/WorkItemTypes.ts +++ b/src/api/WorkItemTypes.ts @@ -66,14 +66,7 @@ export class WorkItemTypes extends BaseResource { /** * List work item types with optional filtering */ - async list( - workspaceSlug: string, - projectId: string, - params?: ListWorkItemTypesParams - ): Promise> { - return this.get>( - `/workspaces/${workspaceSlug}/projects/${projectId}/work-item-types/`, - params - ); + async list(workspaceSlug: string, projectId: string, params?: ListWorkItemTypesParams): Promise { + return this.get(`/workspaces/${workspaceSlug}/projects/${projectId}/work-item-types/`, params); } } diff --git a/src/api/WorkItems/Comments.ts b/src/api/WorkItems/Comments.ts index cd5a025..268ac36 100644 --- a/src/api/WorkItems/Comments.ts +++ b/src/api/WorkItems/Comments.ts @@ -1,10 +1,12 @@ import { BaseResource } from "../BaseResource"; import { Configuration } from "../../Configuration"; import { + ListCommentsParams, WorkItemComment, WorkItemCommentCreateRequest, WorkItemCommentUpdateRequest, } from "../../models/Comment"; +import { PaginatedResponse } from "../../models/common"; /** * WorkItemComments API resource @@ -36,9 +38,9 @@ export class Comments extends BaseResource { workspaceSlug: string, projectId: string, workItemId: string, - params?: any - ): Promise { - return this.get( + params?: ListCommentsParams + ): Promise> { + return this.get>( `/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${workItemId}/comments/`, params ); @@ -78,12 +80,7 @@ export class Comments extends BaseResource { /** * Delete a comment */ - async delete( - workspaceSlug: string, - projectId: string, - workItemId: string, - commentId: string - ): Promise { + async delete(workspaceSlug: string, projectId: string, workItemId: string, commentId: string): Promise { return this.httpDelete( `/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${workItemId}/comments/${commentId}/` ); diff --git a/src/api/WorkItems/Relations.ts b/src/api/WorkItems/Relations.ts index bd9a9cc..b798f84 100644 --- a/src/api/WorkItems/Relations.ts +++ b/src/api/WorkItems/Relations.ts @@ -48,11 +48,7 @@ export class Relations extends BaseResource { /** * List relations for a work item */ - async list( - workspaceSlug: string, - projectId: string, - workItemId: string - ): Promise { + async list(workspaceSlug: string, projectId: string, workItemId: string): Promise { return this.get( `/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${workItemId}/relations/` ); diff --git a/src/api/WorkItems/index.ts b/src/api/WorkItems/index.ts index e81eac3..851f508 100644 --- a/src/api/WorkItems/index.ts +++ b/src/api/WorkItems/index.ts @@ -42,23 +42,12 @@ export class WorkItems extends BaseResource { /** * Create a new work item */ - async create( - workspaceSlug: string, - projectId: string, - createWorkItem: CreateWorkItem - ): Promise { - return this.post( - `/workspaces/${workspaceSlug}/projects/${projectId}/work-items/`, - createWorkItem - ); + async create(workspaceSlug: string, projectId: string, createWorkItem: CreateWorkItem): Promise { + return this.post(`/workspaces/${workspaceSlug}/projects/${projectId}/work-items/`, createWorkItem); } // method overloads - async retrieve( - workspaceSlug: string, - projectId: string, - workItemId: string - ): Promise; + async retrieve(workspaceSlug: string, projectId: string, workItemId: string): Promise; async retrieve( workspaceSlug: string, @@ -76,10 +65,9 @@ export class WorkItems extends BaseResource { workItemId: string, expand?: E[] ): Promise> { - return this.get>( - `/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${workItemId}/`, - { expand: expand?.join(",") } - ); + return this.get>(`/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${workItemId}/`, { + expand: expand?.join(","), + }); } /** @@ -100,14 +88,8 @@ export class WorkItems extends BaseResource { /** * Delete a work item */ - async delete( - workspaceSlug: string, - projectId: string, - workItemId: string - ): Promise { - return this.httpDelete( - `/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${workItemId}/` - ); + async delete(workspaceSlug: string, projectId: string, workItemId: string): Promise { + return this.httpDelete(`/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${workItemId}/`); } /** @@ -125,10 +107,7 @@ export class WorkItems extends BaseResource { } // method overloads - async retrieveByIdentifier( - workspaceSlug: string, - identifier: string - ): Promise; + async retrieveByIdentifier(workspaceSlug: string, identifier: string): Promise; async retrieveByIdentifier( workspaceSlug: string, @@ -142,24 +121,19 @@ export class WorkItems extends BaseResource { identifier: string, expand?: E[] ): Promise | WorkItemBase> { - return this.get>( - `/workspaces/${workspaceSlug}/work-items/${identifier}/`, - { expand: expand?.join(",") } - ); + return this.get>(`/workspaces/${workspaceSlug}/work-items/${identifier}/`, { + expand: expand?.join(","), + }); } /** * Search work items */ - async search( - workspaceSlug: string, - projectId: string, - query: string, - params?: any - ): Promise { - return this.get( - `/workspaces/${workspaceSlug}/work-items/search/`, - { ...params, search: query, project: projectId } - ); + async search(workspaceSlug: string, query: string, projectId?: string, params?: any): Promise { + return this.get(`/workspaces/${workspaceSlug}/work-items/search/`, { + ...params, + search: query, + project: projectId, + }); } } diff --git a/src/models/Customer.ts b/src/models/Customer.ts index 46ef476..ea30f36 100644 --- a/src/models/Customer.ts +++ b/src/models/Customer.ts @@ -39,6 +39,10 @@ export interface CustomerPropertyValue extends BaseModel { [key: string]: any; } +export interface CustomPropertyValueResponse { + [propertyId: string]: any[]; +} + export type UpdateCustomerPropertyValue = { values: any[]; }; @@ -140,3 +144,16 @@ export interface UpdateCustomerPropertyRequest { created_by?: string; updated_by?: string; } + +export interface LinkIssuesToCustomerResponse { + message: string; + linked_issues: LinkedIssue[]; +} + +export interface LinkedIssue { + id: string; + name: string; + sequence_id: number; + project_id: string; + project__identifier: string; +} diff --git a/src/models/Cycle.ts b/src/models/Cycle.ts index dd7a31c..bc58bc8 100644 --- a/src/models/Cycle.ts +++ b/src/models/Cycle.ts @@ -21,7 +21,9 @@ export interface CycleWorkItem { export interface Cycle extends BaseModel { name: string; description?: string; + // YYYY-MM-DD format start_date?: string; + // YYYY-MM-DD format end_date?: string; view_props?: any; sort_order?: number; @@ -38,12 +40,15 @@ export interface Cycle extends BaseModel { export interface CreateCycleRequest { name: string; description?: string; + // YYYY-MM-DD format start_date?: string; + // YYYY-MM-DD format end_date?: string; owned_by: string; external_source?: string; external_id?: string; timezone?: TimezoneEnum; + project_id: string; } export interface UpdateCycleRequest { diff --git a/src/models/WorkItem.ts b/src/models/WorkItem.ts index b0901ea..a82219d 100644 --- a/src/models/WorkItem.ts +++ b/src/models/WorkItem.ts @@ -57,6 +57,13 @@ export interface CreateWorkItem { state?: string; assignees?: string[]; labels?: string[]; + parent?: string; + estimate_point?: string; + type?: string; + module?: string; + target_date?: string; + start_date?: string; + priority?: PriorityEnum; } export interface UpdateWorkItem { @@ -65,6 +72,13 @@ export interface UpdateWorkItem { state?: string; assignees?: string[]; labels?: string[]; + parent?: string; + estimate_point?: string; + type?: string; + module?: string; + target_date?: string; + start_date?: string; + priority?: PriorityEnum; } export interface ListWorkItemsParams { diff --git a/src/models/index.ts b/src/models/index.ts index 70a7609..bbac8b4 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -6,3 +6,5 @@ export * from "./Attachment"; export * from "./Link"; export * from "./Customer"; export * from "./Page"; +export * from "./Cycle"; +export * from "./Module"; diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index cc5f016..0000000 --- a/tests/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# Test Configuration - -This directory contains test files and configuration for the Plane Node SDK. - -## Test Configuration Setup - -The test constants are configurable through environment variables. This allows each developer to use their own test environment without modifying shared files. - -### Option 1: Environment Variables (Recommended) - -Set the following environment variables before running tests: - -```bash -export TEST_WORKSPACE_SLUG="your-workspace-slug" -export TEST_PROJECT_ID="your-project-id" -export TEST_USER_ID="your-user-id" -export TEST_WORK_ITEM_ID="your-work-item-id" -export TEST_WORK_ITEM_ID_2="your-second-work-item-id" -export TEST_CUSTOMER_ID="your-customer-id" -export TEST_WORK_ITEM_TYPE_ID="your-work-item-type-id" -export TEST_CUSTOM_TEXT_PROPERTY_ID="your-custom-text-property-id" -``` - -### Option 2: .env.test File - -Create a `.env.test` file in the project root with your configuration: - -```bash -# Copy the example and update with your values -cp env.example .env.test -``` - -Then update the values in `.env.test` with your test environment details. - -### Option 3: Direct File Modification - -If you prefer to modify the constants directly, edit `tests/constants.ts` and update the fallback values. Note that this will affect all developers, so use this option carefully. - -## Running Tests - -After setting up your configuration, run the tests: - -```bash -npm test -# or -pnpm test -``` - -## Default Values - -The constants file includes default values that work with the current test environment. If you don't set environment variables, these defaults will be used. diff --git a/tests/customers/customers.test.ts b/tests/customers/customers.test.ts deleted file mode 100644 index 83a56db..0000000 --- a/tests/customers/customers.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { PlaneClient } from "../../src/client/plane-client"; -import { config } from "../constants"; -import { createTestClient } from "../test-utils"; - -export async function testCustomers() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - - const customer = await createCustomer(client, workspaceSlug); - console.log("Created customer: ", customer); - - const retrievedCustomer = await retrieveCustomer( - client, - workspaceSlug, - customer.id - ); - console.log("Retrieved customer: ", retrievedCustomer); - - const updatedCustomer = await updateCustomer( - client, - workspaceSlug, - customer.id - ); - console.log("Updated customer: ", updatedCustomer); - - const customers = await listCustomers(client, workspaceSlug); - console.log("Listed customers: ", customers); - - await deleteCustomer(client, workspaceSlug, customer.id); - console.log("Deleted customer: ", customer.id); -} - -async function createCustomer(client: PlaneClient, workspaceSlug: string) { - const customer = await client.customers.create(workspaceSlug, { - name: `Test Customer ${new Date().getTime()}`, - description: "Test Customer Description", - }); - return customer; -} - -async function retrieveCustomer( - client: PlaneClient, - workspaceSlug: string, - customerId: string -) { - const customer = await client.customers.retrieve(workspaceSlug, customerId); - return customer; -} - -async function updateCustomer( - client: PlaneClient, - workspaceSlug: string, - customerId: string -) { - return await client.customers.update(workspaceSlug, customerId, { - name: `Updated Test Customer ${new Date().getTime()}`, - description: "Updated Test Customer Description", - }); -} - -async function listCustomers(client: PlaneClient, workspaceSlug: string) { - const customers = await client.customers.list(workspaceSlug, { - limit: 10, - offset: 0, - }); - return customers; -} - -async function deleteCustomer( - client: PlaneClient, - workspaceSlug: string, - customerId: string -) { - await client.customers.delete(workspaceSlug, customerId); -} - -if (require.main === module) { - testCustomers().catch(console.error); -} diff --git a/tests/customers/properties-options.test.ts b/tests/customers/properties-options.test.ts deleted file mode 100644 index e00cb01..0000000 --- a/tests/customers/properties-options.test.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { PlaneClient } from "../../src/client/plane-client"; -import { config } from "../constants"; -import { createTestClient } from "../test-utils"; - -export async function testCustomersPropertiesOptionsAndValues() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const customerId = config.customerId; - - const customerProperty = await createCustomerProperty(client, workspaceSlug); - console.log("Created customer property: ", customerProperty); - - if (!customerProperty.id) { - throw new Error("Customer property ID is required"); - } - - const retrievedCustomerProperty = await retrieveCustomerProperty( - client, - workspaceSlug, - customerProperty.id - ); - console.log("Retrieved customer property: ", retrievedCustomerProperty); - - const updatedCustomerProperty = await updateCustomerProperty( - client, - workspaceSlug, - customerProperty.id - ); - console.log("Updated customer property: ", updatedCustomerProperty); - - const customerProperties = await listCustomerProperties( - client, - workspaceSlug - ); - console.log("Listed customer properties: ", customerProperties); - - // before deleting we test the property values - const customerPropertyValue = await updateCustomerPropertyValue( - client, - workspaceSlug, - customerId, - customerProperty.id - ); - console.log( - "Updated customer property value: ", - customerProperty.id, - customerPropertyValue - ); - - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const allCustomerPropertyValues = await listCustomerPropertyValues( - client, - workspaceSlug, - customerId - ); - console.log("Listed customer property values: ", allCustomerPropertyValues); - - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const retrievedCustomerPropertyValue = await retrieveCustomerPropertyValue( - client, - workspaceSlug, - customerId, - customerProperty.id - ); - console.log( - "Retrieved customer property value: ", - retrievedCustomerPropertyValue - ); - - await new Promise((resolve) => setTimeout(resolve, 1000)); - - await deleteCustomerProperty(client, workspaceSlug, customerProperty.id); - console.log("Deleted customer property: ", customerProperty.id); -} - -// ===== CUSTOMER PROPERTY API METHODS ===== -async function createCustomerProperty( - client: PlaneClient, - workspaceSlug: string -) { - const customerProperty = - await client.customers.properties.createPropertyDefinition(workspaceSlug, { - name: `test-customer-property-${new Date().getTime()}`, - display_name: `Test Customer Property ${new Date().getTime()}`, - description: "Test Customer Property Description", - property_type: "TEXT", - }); - return customerProperty; -} - -async function retrieveCustomerProperty( - client: PlaneClient, - workspaceSlug: string, - propertyId: string -) { - const customerProperty = - await client.customers.properties.retrievePropertyDefinition( - workspaceSlug, - propertyId - ); - return customerProperty; -} - -async function updateCustomerProperty( - client: PlaneClient, - workspaceSlug: string, - propertyId: string -) { - return await client.customers.properties.updatePropertyDefinition( - workspaceSlug, - propertyId, - { - display_name: `Updated Test Customer Property ${new Date().getTime()}`, - description: "Updated Test Customer Property Description", - } - ); -} - -async function listCustomerProperties( - client: PlaneClient, - workspaceSlug: string -) { - return await client.customers.properties.listPropertyDefinitions( - workspaceSlug, - { - limit: 10, - offset: 0, - } - ); -} - -async function deleteCustomerProperty( - client: PlaneClient, - workspaceSlug: string, - propertyId: string -) { - return await client.customers.properties.deletePropertyDefinition( - workspaceSlug, - propertyId - ); -} - -// ===== CUSTOMER PROPERTY VALUES API METHODS ===== -async function updateCustomerPropertyValue( - client: PlaneClient, - workspaceSlug: string, - customerId: string, - propertyId: string -) { - return await client.customers.properties.updateValue( - workspaceSlug, - customerId, - propertyId, - { - values: ["Property Value Updated"], - } - ); -} - -async function listCustomerPropertyValues( - client: PlaneClient, - workspaceSlug: string, - customerId: string -) { - return await client.customers.properties.listValues( - workspaceSlug, - customerId - ); -} - -async function retrieveCustomerPropertyValue( - client: PlaneClient, - workspaceSlug: string, - customerId: string, - propertyId: string -) { - return await client.customers.properties.retrieveValue( - workspaceSlug, - customerId, - propertyId - ); -} - -if (require.main === module) { - testCustomersPropertiesOptionsAndValues().catch(console.error); -} diff --git a/tests/customers/requests.test.ts b/tests/customers/requests.test.ts deleted file mode 100644 index 1c70ff4..0000000 --- a/tests/customers/requests.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { PlaneClient } from "../../src/client/plane-client"; -import { config } from "../constants"; -import { createTestClient } from "../test-utils"; - -export async function testCustomersRequests() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const customerId = config.customerId; - - const customerRequest = await createCustomerRequest( - client, - workspaceSlug, - customerId - ); - console.log("Created customer request: ", customerRequest); - - const retrievedCustomerRequest = await retrieveCustomerRequest( - client, - workspaceSlug, - customerId, - customerRequest.id - ); - console.log("Retrieved customer request: ", retrievedCustomerRequest); - - const updatedCustomerRequest = await updateCustomerRequest( - client, - workspaceSlug, - customerId, - customerRequest.id - ); - console.log("Updated customer request: ", updatedCustomerRequest); - - const customerRequests = await listCustomerRequests( - client, - workspaceSlug, - customerId - ); - console.log("Listed customer requests: ", customerRequests); - - await deleteCustomerRequest( - client, - workspaceSlug, - customerId, - customerRequest.id - ); - console.log("Deleted customer request: ", customerRequest.id); -} - -async function createCustomerRequest( - client: PlaneClient, - workspaceSlug: string, - customerId: string -) { - const customerRequest = await client.customers.requests.create( - workspaceSlug, - customerId, - { - name: `Test Customer Request ${new Date().getTime()}`, - description: "Test Customer Request Description", - } - ); - return customerRequest; -} - -async function retrieveCustomerRequest( - client: PlaneClient, - workspaceSlug: string, - customerId: string, - requestId: string -) { - const customerRequest = await client.customers.requests.retrieve( - workspaceSlug, - customerId, - requestId - ); - return customerRequest; -} - -async function updateCustomerRequest( - client: PlaneClient, - workspaceSlug: string, - customerId: string, - requestId: string -) { - return await client.customers.requests.update( - workspaceSlug, - customerId, - requestId, - { - name: `Updated Test Customer Request ${new Date().getTime()}`, - description: "Updated Test Customer Request Description", - } - ); -} - -async function listCustomerRequests( - client: PlaneClient, - workspaceSlug: string, - customerId: string -) { - const customerRequests = await client.customers.requests.list( - workspaceSlug, - customerId, - { - limit: 10, - offset: 0, - } - ); - return customerRequests; -} - -async function deleteCustomerRequest( - client: PlaneClient, - workspaceSlug: string, - customerId: string, - requestId: string -) { - await client.customers.requests.delete(workspaceSlug, customerId, requestId); -} - -if (require.main === module) { - testCustomersRequests().catch(console.error); -} diff --git a/tests/customers/work-items.test.ts b/tests/customers/work-items.test.ts deleted file mode 100644 index ef63d13..0000000 --- a/tests/customers/work-items.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { PlaneClient } from "../../src/client/plane-client"; -import { config } from "../constants"; -import { createTestClient } from "../test-utils"; - -export async function testCustomersWorkItems() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const customerId = config.customerId; - const workItemId = config.workItemId; - - const customer = await linkWorkItemsToCustomer( - client, - workspaceSlug, - customerId, - [workItemId] - ); - console.log("Linked work items to customer: ", customer); - - const retrievedCustomerWorkItems = await listCustomerWorkItems( - client, - workspaceSlug, - customerId - ); - console.log("Retrieved customer work items: ", retrievedCustomerWorkItems); - - const updatedCustomer = await unlinkWorkItemFromCustomer( - client, - workspaceSlug, - customerId, - workItemId - ); - console.log("Unlinked work item from customer: ", updatedCustomer); -} - -async function linkWorkItemsToCustomer( - client: PlaneClient, - workspaceSlug: string, - customerId: string, - issueIds: string[] -) { - const customer = await client.customers.linkIssuesToCustomer( - workspaceSlug, - customerId, - issueIds - ); - return customer; -} - -async function listCustomerWorkItems( - client: PlaneClient, - workspaceSlug: string, - customerId: string -) { - const customer = await client.customers.listCustomerIssues( - workspaceSlug, - customerId - ); - return customer; -} - -async function unlinkWorkItemFromCustomer( - client: PlaneClient, - workspaceSlug: string, - customerId: string, - issueId: string -) { - return await client.customers.unlinkIssueFromCustomer( - workspaceSlug, - customerId, - issueId - ); -} - -if (require.main === module) { - testCustomersWorkItems().catch(console.error); -} diff --git a/tests/cycle.test.ts b/tests/cycle.test.ts deleted file mode 100644 index d9d256b..0000000 --- a/tests/cycle.test.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { PlaneClient } from "../src/client/plane-client"; -import { UpdateCycleRequest } from "../src/models/Cycle"; -import { config } from "./constants"; -import { createTestClient } from "./test-utils"; - -export async function testCycles() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const projectId = config.projectId; - const workItemId = config.workItemId; - - const project = await client.projects.retrieve(workspaceSlug, projectId); - if (!project.cycle_view) { - await client.projects.update(workspaceSlug, projectId, { - cycle_view: true, - }); - } - - const cycle = await createCycle(client, workspaceSlug, projectId); - console.log("Created cycle: ", cycle); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const retrievedCycle = await retrieveCycle( - client, - workspaceSlug, - projectId, - cycle.id - ); - console.log("Retrieved cycle: ", retrievedCycle); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const updatedCycle = await updateCycle( - client, - workspaceSlug, - projectId, - cycle.id, - { - name: "Updated Test Cycle", - description: "Updated Test Cycle Description", - } - ); - console.log("Updated cycle: ", updatedCycle); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const cycles = await listCycles(client, workspaceSlug, projectId); - console.log("Listed cycles: ", cycles); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - await addWorkItemsToCycle( - client, - workspaceSlug, - projectId, - cycle.id, - workItemId - ); - console.log("Added work item to cycle: ", workItemId); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const itemsInCycle = await listWorkItemsInCycle( - client, - workspaceSlug, - projectId, - cycle.id - ); - console.log("Listed work items in cycle: ", itemsInCycle); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const removedItem = await removeWorkItemFromCycle( - client, - workspaceSlug, - projectId, - cycle.id, - workItemId - ); - console.log("Removed work item from cycle: ", removedItem); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - // Create a second cycle for transfer testing - const cycle2 = await createCycle(client, workspaceSlug, projectId); - console.log("Created second cycle for transfer: ", cycle2); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - // create a new work item for transfer testing - const workItem2 = await client.workItems.create(workspaceSlug, projectId, { - name: "Test Work Item 2", - }); - console.log("Created new work item for transfer: ", workItem2); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - // Add work item back to first cycle for transfer test - await addWorkItemsToCycle( - client, - workspaceSlug, - projectId, - cycle.id, - workItemId - ); - console.log("Re-added work item to first cycle: ", workItemId); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - await transferWorkItemsToAnotherCycle( - client, - workspaceSlug, - projectId, - cycle.id, - cycle2.id - ); - console.log("Transferred work items to another cycle"); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - // await archiveCycle(client, workspaceSlug, projectId, cycle.id); - // console.log("Archived cycle: ", cycle.id); - // await new Promise((resolve) => setTimeout(resolve, 1000)); - - // const archivedCycles = await listArchivedCycles( - // client, - // workspaceSlug, - // projectId - // ); - // console.log("Listed archived cycles: ", archivedCycles); - // await new Promise((resolve) => setTimeout(resolve, 1000)); - - // await unArchiveCycle(client, workspaceSlug, projectId, cycle.id); - // console.log("Unarchived cycle: ", cycle.id); - // await new Promise((resolve) => setTimeout(resolve, 1000)); - - await deleteCycle(client, workspaceSlug, projectId, cycle.id); - console.log("Deleted cycle: ", cycle.id); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - await deleteCycle(client, workspaceSlug, projectId, cycle2.id); - console.log("Deleted second cycle: ", cycle2.id); -} - -async function createCycle( - client: PlaneClient, - workspaceSlug: string, - projectId: string -) { - return await client.cycles.create(workspaceSlug, projectId, { - name: "Test Cycle", - description: "Test Cycle Description", - owned_by: config.userId, - }); -} - -async function retrieveCycle( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - cycleId: string -) { - return await client.cycles.retrieve(workspaceSlug, projectId, cycleId); -} - -async function updateCycle( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - cycleId: string, - cycle: UpdateCycleRequest -) { - return await client.cycles.update(workspaceSlug, projectId, cycleId, cycle); -} - -async function listCycles( - client: PlaneClient, - workspaceSlug: string, - projectId: string -) { - return await client.cycles.list(workspaceSlug, projectId); -} - -async function deleteCycle( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - cycleId: string -) { - return await client.cycles.delete(workspaceSlug, projectId, cycleId); -} - -async function addWorkItemsToCycle( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - cycleId: string, - workItemId: string -) { - return await client.cycles.addWorkItemsToCycle( - workspaceSlug, - projectId, - cycleId, - [workItemId] - ); -} - -async function listWorkItemsInCycle( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - cycleId: string -) { - return await client.cycles.listWorkItemsInCycle( - workspaceSlug, - projectId, - cycleId - ); -} - -async function removeWorkItemFromCycle( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - cycleId: string, - workItemId: string -) { - return await client.cycles.removeWorkItemFromCycle( - workspaceSlug, - projectId, - cycleId, - workItemId - ); -} - -async function transferWorkItemsToAnotherCycle( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - cycleId: string, - newCycleId: string -) { - return await client.cycles.transferWorkItemsToAnotherCycle( - workspaceSlug, - projectId, - cycleId, - { new_cycle_id: newCycleId } - ); -} - -async function archiveCycle( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - cycleId: string -) { - return await client.cycles.archive(workspaceSlug, projectId, cycleId); -} - -async function listArchivedCycles( - client: PlaneClient, - workspaceSlug: string, - projectId: string -) { - return await client.cycles.listArchived(workspaceSlug, projectId); -} - -async function unArchiveCycle( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - cycleId: string -) { - return await client.cycles.unArchive(workspaceSlug, projectId, cycleId); -} - -async function justArchiveCycle() { - const client = createTestClient(); - const workspaceSlug = config.workspaceSlug; - const projectId = config.projectId; - - const project = await client.projects.retrieve(workspaceSlug, projectId); - if (!project.cycle_view) { - await client.projects.update(workspaceSlug, projectId, { - cycle_view: true, - }); - } - - return await client.cycles.archive( - workspaceSlug, - projectId, - "e1ca5ee2-c968-4be5-80fa-f6dff66bea98" - ); -} - -if (require.main === module) { - testCycles().catch(console.error); -} diff --git a/tests/e2e/config.ts b/tests/e2e/config.ts new file mode 100644 index 0000000..1a5a7cb --- /dev/null +++ b/tests/e2e/config.ts @@ -0,0 +1,3 @@ +export const e2eConfig = { + workspaceSlug: process.env.TEST_WORKSPACE_SLUG!, +}; diff --git a/tests/epic.test.ts b/tests/epic.test.ts deleted file mode 100644 index 3c6218e..0000000 --- a/tests/epic.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { PlaneClient } from "../src/client/plane-client"; -import { config } from "./constants"; -import { createTestClient } from "./test-utils"; - -export async function testEpics() { - const client = createTestClient(); - - const project = await client.projects.retrieve( - config.workspaceSlug, - config.projectId - ); - - const epics = await client.epics.list(config.workspaceSlug, config.projectId); - - console.log("Listed epics: ", epics); - - const epic = await client.epics.retrieve( - config.workspaceSlug, - config.projectId, - epics.results[0]!.id! - ); - - console.log("Retrieved epic: ", epic); -} - -if (require.main === module) { - testEpics().catch(console.error); -} diff --git a/tests/helpers/conditional-tests.ts b/tests/helpers/conditional-tests.ts new file mode 100644 index 0000000..a176b97 --- /dev/null +++ b/tests/helpers/conditional-tests.ts @@ -0,0 +1,14 @@ +/** + * Test utilities for conditional test execution based on configuration + */ + +/** + * Creates a conditional describe block that skips all tests if condition is false + * @param condition - Boolean condition to check + * @param name - Name of the test suite + * @param fn - Test suite function + * @returns Jest describe block (either normal or skipped) + */ +export const describeIf = (condition: boolean, name: string, fn: () => void) => { + return condition ? describe(name, fn) : describe.skip(name, fn); +}; diff --git a/tests/test-utils.ts b/tests/helpers/test-utils.ts similarity index 60% rename from tests/test-utils.ts rename to tests/helpers/test-utils.ts index 870e628..dfb45ea 100644 --- a/tests/test-utils.ts +++ b/tests/helpers/test-utils.ts @@ -1,4 +1,4 @@ -import { PlaneClient } from "../src/client/plane-client"; +import { PlaneClient } from "../../src/client/plane-client"; /** * Creates a configured PlaneClient instance for testing @@ -8,7 +8,6 @@ export function createTestClient(): PlaneClient { return new PlaneClient({ apiKey: process.env.PLANE_API_KEY!, baseUrl: process.env.PLANE_BASE_URL!, - enableLogging: true, }); } @@ -20,3 +19,13 @@ export function createTestClient(): PlaneClient { export function wait(seconds = 60): Promise { return new Promise((resolve) => setTimeout(resolve, seconds * 1000)); } + +/** + * Randomizes a name by appending a random string to the end + * @param name - The name to randomize + * @param length - The length of the random string to append + * @returns The randomized name + */ +export function randomizeName(prefix: string = ""): string { + return prefix + Math.random().toString(36).substring(2, 10); +} diff --git a/tests/intake.test.ts b/tests/intake.test.ts deleted file mode 100644 index 30d1f3b..0000000 --- a/tests/intake.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { PlaneClient } from "../src/client/plane-client"; -import { PaginatedResponse } from "../src/models/common"; -import { IntakeWorkItem } from "../src/models/Intake"; -import { config } from "./constants"; -import { createTestClient } from "./test-utils"; - -export async function testIntake() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const projectId = config.projectId; - - // enable intake if didn't already - const updatedProject = await client.projects.update( - workspaceSlug, - projectId, - { - intake_view: true, - } - ); - - const intakeWorkItem = await createIntake(client, workspaceSlug, projectId); - console.log("Created intake: ", intakeWorkItem); - - const retrievedIntake = await retrieveIntake( - client, - workspaceSlug, - projectId, - intakeWorkItem.issue! - ); - console.log("Retrieved intake: ", retrievedIntake); - - const updatedIntake = await updateIntake( - client, - workspaceSlug, - projectId, - intakeWorkItem.issue!, - intakeWorkItem - ); - console.log("Updated intake: ", updatedIntake); - - const intakes = await listIntake(client, workspaceSlug, projectId); - console.log("Listed intakes: ", intakes); - - await deleteIntake(client, workspaceSlug, projectId, intakeWorkItem.issue!); - console.log("Intake deleted: ", intakeWorkItem.id); -} - -async function createIntake( - client: PlaneClient, - workspaceSlug: string, - projectId: string -): Promise { - const intake = await client.intake.create(workspaceSlug, projectId, { - issue: { - name: "Test Intake", - description_html: "

Test Intake Description

", - }, - }); - return intake; -} - -async function retrieveIntake( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - intakeWorkItemId: string -): Promise { - const intake = await client.intake.retrieve( - workspaceSlug, - projectId, - intakeWorkItemId - ); - return intake; -} - -async function listIntake( - client: PlaneClient, - workspaceSlug: string, - projectId: string -): Promise> { - const intakes = await client.intake.list(workspaceSlug, projectId); - return intakes; -} - -async function updateIntake( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - intakeWorkItemId: string, - intake: IntakeWorkItem -): Promise { - const updatedIntake = await client.intake.update( - workspaceSlug, - projectId, - intakeWorkItemId, - { - issue: { - name: "Updated Test Intake", - description_html: "

Updated Test Intake Description

", - }, - } - ); - return updatedIntake; -} - -async function deleteIntake( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - intakeWorkItemId: string -): Promise { - await client.intake.delete(workspaceSlug, projectId, intakeWorkItemId); -} - -if (require.main === module) { - testIntake().catch(console.error); -} diff --git a/tests/label.test.ts b/tests/label.test.ts deleted file mode 100644 index 0866399..0000000 --- a/tests/label.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { PlaneClient } from "../src/client/plane-client"; -import { Label } from "../src/models/Label"; -import { config } from "./constants"; -import { createTestClient } from "./test-utils"; - -export async function testLabels() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const projectId = config.projectId; - - const labelObj = await createLabel(client, workspaceSlug, projectId); - console.log("Created label: ", labelObj); - - const retrievedLabel = await retrieveLabel( - client, - workspaceSlug, - projectId, - labelObj.id - ); - console.log("Retrieved label: ", retrievedLabel); - - const updatedLabel = await updateLabel( - client, - workspaceSlug, - projectId, - labelObj.id, - labelObj - ); - console.log("Updated label: ", updatedLabel); - - const labels = await listLabels(client, workspaceSlug, projectId); - console.log("Listed labels: ", labels); - - await deleteLabel(client, workspaceSlug, projectId, labelObj.id); - console.log("Label deleted: ", labelObj.id); -} - -async function createLabel( - client: PlaneClient, - workspaceSlug: string, - projectId: string -) { - const label = await client.labels.create(workspaceSlug, projectId, { - name: "Test Label " + new Date().getTime(), - description: "Test Label Description", - }); - return label; -} - -async function retrieveLabel( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - labelId: string -) { - const label = await client.labels.retrieve(workspaceSlug, projectId, labelId); - return label; -} - -async function updateLabel( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - labelId: string, - label: Label -) { - const updatedLabel = await client.labels.update( - workspaceSlug, - projectId, - labelId, - { - description: "Updated Test Label Description" + new Date().toISOString(), - } - ); - return updatedLabel; -} - -async function deleteLabel( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - labelId: string -) { - await client.labels.delete(workspaceSlug, projectId, labelId); -} - -async function listLabels( - client: PlaneClient, - workspaceSlug: string, - projectId: string -) { - const labels = await client.labels.list(workspaceSlug, projectId); - return labels; -} - -if (require.main === module) { - testLabels().catch(console.error); -} diff --git a/tests/module.test.ts b/tests/module.test.ts deleted file mode 100644 index faecad3..0000000 --- a/tests/module.test.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { PlaneClient } from "../src/client/plane-client"; -import { UpdateModuleRequest } from "../src/models/Module"; -import { config } from "./constants"; -import { createTestClient } from "./test-utils"; - -export async function testModules() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const projectId = config.projectId; - const workItemId = config.workItemId; - - const module = await createModule(client, workspaceSlug, projectId); - console.log("Created module: ", module); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const retrievedModule = await retrieveModule( - client, - workspaceSlug, - projectId, - module.id - ); - console.log("Retrieved module: ", retrievedModule); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const updatedModule = await updateModule( - client, - workspaceSlug, - projectId, - module.id, - module - ); - console.log("Updated module: ", updatedModule); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const modules = await listModules(client, workspaceSlug, projectId); - console.log("Listed modules: ", modules); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - await addWorkItemToModule( - client, - workspaceSlug, - projectId, - module.id, - workItemId - ); - console.log("Added work item to module: ", workItemId); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const itemsInModule = await listWorkItemsInModule( - client, - workspaceSlug, - projectId, - module.id - ); - console.log("Listed work items in module: ", itemsInModule); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const removedItem = await removeWorkItemFromModule( - client, - workspaceSlug, - projectId, - module.id, - workItemId - ); - console.log("Removed work item from module: ", removedItem); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - await deleteModule(client, workspaceSlug, projectId, module.id); - console.log("Deleted module: ", module.id); -} - -async function createModule( - client: PlaneClient, - workspaceSlug: string, - projectId: string -) { - return await client.modules.create(workspaceSlug, projectId, { - name: "Test Module", - description: "Test Description", - }); -} - -async function retrieveModule( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - moduleId: string -) { - return await client.modules.retrieve(workspaceSlug, projectId, moduleId); -} - -async function updateModule( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - moduleId: string, - module: UpdateModuleRequest -) { - return await client.modules.update( - workspaceSlug, - projectId, - moduleId, - module - ); -} - -async function listModules( - client: PlaneClient, - workspaceSlug: string, - projectId: string -) { - return await client.modules.list(workspaceSlug, projectId); -} - -async function deleteModule( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - moduleId: string -) { - return await client.modules.delete(workspaceSlug, projectId, moduleId); -} - -async function addWorkItemToModule( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - moduleId: string, - workItemId: string -) { - return await client.modules.addWorkItemToModule( - workspaceSlug, - projectId, - moduleId, - [workItemId] - ); -} -async function listWorkItemsInModule( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - moduleId: string -) { - return await client.modules.listWorkItemsInModule( - workspaceSlug, - projectId, - moduleId - ); -} - -async function removeWorkItemFromModule( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - moduleId: string, - workItemId: string -) { - return await client.modules.removeWorkItemFromModule( - workspaceSlug, - projectId, - moduleId, - workItemId - ); -} - -if (require.main === module) { - testModules(); -} diff --git a/tests/oauth.test.ts b/tests/oauth.test.ts deleted file mode 100644 index 88cd971..0000000 --- a/tests/oauth.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { PlaneClient } from "../src/client/plane-client"; -import { OAuthClient } from "../src/client/oauth-client"; -import { Configuration } from "../src/Configuration"; - -/** - * Test OAuth functionality with both standalone and integrated approaches - */ -export async function testOAuth() { - console.log("๐Ÿงช Testing OAuth functionality..."); - - try { - // Test 1: Standalone OAuth Client (for app development) - console.log(" ๐Ÿ“‹ Testing standalone OAuth client..."); - - if ( - !process.env.PLANE_BASE_URL || - !process.env.PLANE_CLIENT_ID || - !process.env.PLANE_CLIENT_SECRET || - !process.env.PLANE_REDIRECT_URI || - !process.env.PLANE_APP_INSTALLATION_ID - ) { - console.error("โŒ Skipping oauth tests, missing environment variables"); - return; - } - - const oauthClient = new OAuthClient({ - baseUrl: process.env.PLANE_BASE_URL, - clientId: process.env.PLANE_CLIENT_ID!, - clientSecret: process.env.PLANE_CLIENT_SECRET!, - redirectUri: process.env.PLANE_REDIRECT_URI!, - }); - - const authUrl = oauthClient.getAuthorizationUrl(); - console.log(" โœ… Authorization URL generated:", authUrl); - - const botToken = await oauthClient.getBotToken( - process.env.PLANE_APP_INSTALLATION_ID! - ); - - console.log(" โœ… Bot token generated:", botToken); - - const installations = await oauthClient.getAppInstallations( - botToken.access_token - ); - - console.log(" โœ… App installations generated:", installations); - - // Test authorization URL generation - } catch (error) { - console.error("โŒ OAuth test failed:", error); - throw error; - } -} - -if (require.main === module) { - testOAuth().catch((error) => { - console.error("โŒ OAuth test failed:", error); - process.exit(1); - }); -} diff --git a/tests/page.test.ts b/tests/page.test.ts deleted file mode 100644 index b360aa6..0000000 --- a/tests/page.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import fs from "fs"; -import { PlaneClient } from "../src/client/plane-client"; -import { config } from "./constants"; -import { createTestClient } from "./test-utils"; - -export async function testPage() { - const client = createTestClient(); - - const page = await createPage(client, config.workspaceSlug); - console.log("Created page: ", page); - - const retrievedPage = await retrievePage( - client, - config.workspaceSlug, - page.id - ); - console.log("Retrieved page: ", retrievedPage); - - const projectPage = await createProjectPage( - client, - config.workspaceSlug, - config.projectId - ); - console.log("Created project page: ", projectPage); - - const retrievedProjectPage = await retrieveProjectPage( - client, - config.workspaceSlug, - config.projectId, - projectPage.id - ); - console.log("Retrieved project page: ", retrievedProjectPage); -} - -async function createPage(client: PlaneClient, workspaceSlug: string) { - const content = "

Blank Space

"; - return client.pages.createWorkspacePage(workspaceSlug, { - name: "Test Page Crashable 3", - description_html: content, - }); -} - -async function retrievePage( - client: PlaneClient, - workspaceSlug: string, - pageId: string -) { - return client.pages.retrieveWorkspacePage(workspaceSlug, pageId); -} - -async function createProjectPage( - client: PlaneClient, - workspaceSlug: string, - projectId: string -) { - const content = "

Blank Space

"; - return client.pages.createProjectPage(workspaceSlug, projectId, { - name: "Test Page Crashable 3", - description_html: content, - }); -} - -async function retrieveProjectPage( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - pageId: string -) { - return client.pages.retrieveProjectPage(workspaceSlug, projectId, pageId); -} - -async function testCreatePageFromFile() { - const client = new PlaneClient({ - apiKey: "plane_api_fae8f19b1e884400831413ef3adfb68b", - baseUrl: "https://test-leak.feat.plane.town", - enableLogging: true, - }); - - const file = fs.readFileSync( - "/Users/prashantsurya/Projects/sdks/plane-node-sdk/CrashablePage.html", - "utf8" - ); - - const page = await client.pages.createWorkspacePage("testt", { - name: "Test Page Crashable 3", - description_html: file, - }); - console.log("Created page: ", page); -} - -if (require.main === module) { - testCreatePageFromFile().catch(console.error); -} diff --git a/tests/project.test.ts b/tests/project.test.ts deleted file mode 100644 index 27a9ad2..0000000 --- a/tests/project.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { PlaneClient } from "../src/client/plane-client"; -import { UpdateProject } from "../src/models/Project"; -import { config } from "./constants"; -import { createTestClient } from "./test-utils"; - -export async function testProjects() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - - const project = await createProject(client, workspaceSlug); - console.log("created project", project); - - const retrievedProject = await retrieveProject( - client, - workspaceSlug, - project.id - ); - console.log("retrieved project", retrievedProject); - - const updatedProject = await updateProject( - client, - workspaceSlug, - project.id, - { - name: "Updated Test Project", - description: "Updated Test Project Description", - } - ); - console.log("updated project", updatedProject); - - const projects = await listProjects(client, workspaceSlug); - console.log("listed projects", projects); - - const members = await getMembers(client, workspaceSlug, project.id); - console.log("project members", members); - - await deleteProject(client, workspaceSlug, project.id); - console.log("project deleted", project.id); -} - -async function createProject(client: PlaneClient, workspaceSlug: string) { - const project = await client.projects.create(workspaceSlug, { - name: "Test Project", - description: "Test Project Description", - }); - return project; -} - -async function retrieveProject( - client: PlaneClient, - workspaceSlug: string, - projectId: string -) { - const project = await client.projects.retrieve(workspaceSlug, projectId); - return project; -} - -async function updateProject( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - project: UpdateProject -) { - const updatedProject = await client.projects.update( - workspaceSlug, - projectId, - project - ); - return updatedProject; -} - -async function listProjects(client: PlaneClient, workspaceSlug: string) { - const projects = await client.projects.list(workspaceSlug); - return projects; -} - -async function getMembers( - client: PlaneClient, - workspaceSlug: string, - projectId: string -) { - const members = await client.projects.getMembers(workspaceSlug, projectId); - return members; -} - -async function deleteProject( - client: PlaneClient, - workspaceSlug: string, - projectId: string -) { - await client.projects.delete(workspaceSlug, projectId); -} - -if (require.main === module) { - testProjects().catch(console.error); -} diff --git a/tests/run-all.test.ts b/tests/run-all.test.ts deleted file mode 100644 index 73d5d75..0000000 --- a/tests/run-all.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { wait } from "./test-utils"; - -import { testIntake } from "./intake.test"; -import { testEpics } from "./epic.test"; -import { testPage } from "./page.test"; -import { testProjects } from "./project.test"; -import { testLabels } from "./label.test"; -import { testStates } from "./state.test"; - -import { testWorkItemTypes } from "./work-item-types/types.test"; -import { testWorkItemTypesPropertiesAndOptions } from "./work-item-types/properties-options.test"; -import { testWorkItemPropertiesValues } from "./work-item-types/properties-values.test"; - -import { testWorkItems } from "./work-items/work-items.test"; -import { testWorkLogs } from "./work-items/work-logs.test"; -import { testRelations } from "./work-items/relations.test"; - -import { testModules } from "./module.test"; -import { testCycles } from "./cycle.test"; - -import { testCustomersWorkItems } from "./customers/work-items.test"; -import { testCustomersPropertiesOptionsAndValues } from "./customers/properties-options.test"; -import { testCustomers } from "./customers/customers.test"; -import { testCustomersRequests } from "./customers/requests.test"; - -import { testOAuth } from "./oauth.test"; -import { testComments } from "./work-items/comments.test"; -import { testLinks } from "./work-items/links.test"; -import { testActivities } from "./work-items/activities.test"; - -async function runAllTests() { - await testIntake(); - await testEpics(); - await testPage(); - - await wait(60); - await testProjects(); - await testLabels(); - await testStates(); - await wait(60); - - await testWorkItemTypes(); - await testWorkItemTypesPropertiesAndOptions(); - await testWorkItemPropertiesValues(); - await wait(60); - await testWorkItems(); - await testRelations(); - await testWorkLogs(); - - await testModules(); - await testCycles(); - - await wait(60); - await testCustomers(); - await testCustomersWorkItems(); - await testCustomersPropertiesOptionsAndValues(); - await testCustomersRequests(); - - await wait(60); - await testComments(); - await testLinks(); - await testActivities(); - - await wait(60); - await testOAuth(); -} - -if (require.main === module) { - runAllTests().catch(console.error); -} diff --git a/tests/state.test.ts b/tests/state.test.ts deleted file mode 100644 index 57763f6..0000000 --- a/tests/state.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { PlaneClient } from "../src/client/plane-client"; -import { State } from "../src/models/State"; -import { config } from "./constants"; -import { createTestClient } from "./test-utils"; - -export async function testStates() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const projectId = config.projectId; - - const stateObj = await createState(client, workspaceSlug, projectId); - console.log("Created state: ", stateObj); - - const retrievedState = await retrieveState( - client, - workspaceSlug, - projectId, - stateObj.id - ); - console.log("Retrieved state: ", retrievedState); - - const updatedState = await updateState( - client, - workspaceSlug, - projectId, - stateObj.id, - stateObj - ); - console.log("Updated state: ", updatedState); - - const states = await listStates(client, workspaceSlug, projectId); - console.log("Listed states: ", states); - - await deleteState(client, workspaceSlug, projectId, stateObj.id); - console.log("State deleted: ", stateObj.id); -} - -async function createState( - client: PlaneClient, - workspaceSlug: string, - projectId: string -) { - const state = await client.states.create(workspaceSlug, projectId, { - name: "Test State " + new Date().getTime(), - description: "Test State Description", - group: "started", - color: "#9AA4BC", - }); - return state; -} - -async function retrieveState( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - stateId: string -) { - const state = await client.states.retrieve(workspaceSlug, projectId, stateId); - return state; -} - -async function updateState( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - stateId: string, - state: State -) { - const updatedState = await client.states.update( - workspaceSlug, - projectId, - stateId, - { - description: "Updated Test State Description", - } - ); - return updatedState; -} - -async function listStates( - client: PlaneClient, - workspaceSlug: string, - projectId: string -) { - const states = await client.states.list(workspaceSlug, projectId); - return states; -} - -async function deleteState( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - stateId: string -) { - await client.states.delete(workspaceSlug, projectId, stateId); -} - -if (require.main === module) { - testStates().catch(console.error); -} diff --git a/tests/unit/README.md b/tests/unit/README.md new file mode 100644 index 0000000..0be81a0 --- /dev/null +++ b/tests/unit/README.md @@ -0,0 +1,91 @@ +# Unit Tests + +This directory contains Jest-based unit tests for the Plane Node SDK. All tests use conditional execution patterns that gracefully skip tests when required configuration is not available. + +## Test Structure + +All tests follow a consistent pattern: +- Written using Jest with `describe`, `it`, `beforeAll`, and `afterAll` blocks +- Use conditional `describeIf` helper to skip tests when required config is missing +- Include proper cleanup in `afterAll` hooks to remove test data +- Use `randomizeName()` utility to generate unique test data + +## Test Configuration + +Tests require environment variables to run. If these are not set, the tests will be skipped automatically (no failures). + +### Required Environment Variables + +Set the following environment variables before running tests: + +```bash +export TEST_WORKSPACE_SLUG="your-workspace-slug" +export TEST_PROJECT_ID="your-project-id" +export TEST_USER_ID="your-user-id" +export TEST_WORK_ITEM_ID="your-work-item-id" +export TEST_WORK_ITEM_ID_2="your-second-work-item-id" +export TEST_CUSTOMER_ID="your-customer-id" +export TEST_WORK_ITEM_TYPE_ID="your-work-item-type-id" +export TEST_CUSTOM_TEXT_PROPERTY_ID="your-custom-text-property-id" +``` + +### Using .env File + +Create a `.env` file in the project root with your configuration: + +```bash +# Copy the example and update with your values +cp env.example .env +``` + +Then update the values with your test environment details. + +## Running Tests + +```bash +# Run all tests +pnpm test + +# Run a specific test file +pnpm test label.test.ts + +# Run tests in a specific directory +pnpm test customers + +# Run tests matching a pattern +pnpm test work-items + +# Run tests in watch mode +pnpm test --watch + +# Run tests with coverage +pnpm test --coverage +``` + +## Test Organization + +``` +tests/unit/ +โ”œโ”€โ”€ constants.ts # Test configuration +โ”œโ”€โ”€ helpers/ +โ”‚ โ”œโ”€โ”€ conditional-tests.ts # Conditional describe helper +โ”‚ โ””โ”€โ”€ test-utils.ts # Test utilities +โ”œโ”€โ”€ customers/ # Customer API tests +โ”œโ”€โ”€ work-items/ # Work Items API tests +โ”œโ”€โ”€ work-item-types/ # Work Item Types API tests +โ”œโ”€โ”€ cycle.test.ts # Cycle API tests +โ”œโ”€โ”€ epic.test.ts # Epic API tests +โ”œโ”€โ”€ intake.test.ts # Intake API tests +โ”œโ”€โ”€ label.test.ts # Label API tests +โ”œโ”€โ”€ module.test.ts # Module API tests +โ”œโ”€โ”€ oauth.test.ts # OAuth API tests +โ”œโ”€โ”€ page.test.ts # Page API tests +โ”œโ”€โ”€ project.test.ts # Project API tests +โ””โ”€โ”€ state.test.ts # State API tests +``` + +## Test Behavior + +- **With Config**: Tests run normally and verify API operations +- **Without Config**: Tests are skipped gracefully (shown as "skipped" in test output) +- **Cleanup**: All tests clean up created resources in `afterAll` hooks diff --git a/tests/constants.ts b/tests/unit/constants.ts similarity index 100% rename from tests/constants.ts rename to tests/unit/constants.ts diff --git a/tests/unit/customers/customers.test.ts b/tests/unit/customers/customers.test.ts new file mode 100644 index 0000000..5f9f55b --- /dev/null +++ b/tests/unit/customers/customers.test.ts @@ -0,0 +1,74 @@ +import { PlaneClient } from "../../../src/client/plane-client"; +import { Customer } from "../../../src/models/Customer"; +import { config } from "../constants"; +import { createTestClient, randomizeName } from "../../helpers/test-utils"; +import { describeIf as describe } from "../../helpers/conditional-tests"; + +describe(!!config.workspaceSlug, "Customer API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let customer: Customer; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + }); + + afterAll(async () => { + // Clean up created customer + if (customer?.id) { + try { + await client.customers.delete(workspaceSlug, customer.id); + } catch (error) { + console.warn("Failed to delete customer:", error); + } + } + }); + + it("should create a customer", async () => { + customer = await client.customers.create(workspaceSlug, { + name: randomizeName("Test Customer"), + description: "Test Customer Description", + }); + + expect(customer).toBeDefined(); + expect(customer.id).toBeDefined(); + expect(customer.name).toContain("Test Customer"); + expect(customer.description).toBe("Test Customer Description"); + }); + + it("should retrieve a customer", async () => { + const retrievedCustomer = await client.customers.retrieve(workspaceSlug, customer.id!); + + expect(retrievedCustomer).toBeDefined(); + expect(retrievedCustomer.id).toBe(customer.id); + expect(retrievedCustomer.name).toBe(customer.name); + expect(retrievedCustomer.description).toBe(customer.description); + }); + + it("should update a customer", async () => { + const updatedCustomer = await client.customers.update(workspaceSlug, customer.id!, { + name: randomizeName("Updated Test Customer"), + description: "Updated Test Customer Description", + }); + + expect(updatedCustomer).toBeDefined(); + expect(updatedCustomer.id).toBe(customer.id); + expect(updatedCustomer.name).toContain("Updated Test Customer"); + expect(updatedCustomer.description).toBe("Updated Test Customer Description"); + }); + + it("should list customers", async () => { + const customers = await client.customers.list(workspaceSlug, { + limit: 10, + offset: 0, + }); + + expect(customers).toBeDefined(); + expect(Array.isArray(customers.results)).toBe(true); + expect(customers.results.length).toBeGreaterThan(0); + + const foundCustomer = customers.results.find((c) => c.id === customer.id); + expect(foundCustomer).toBeDefined(); + }); +}); diff --git a/tests/unit/customers/properties-options.test.ts b/tests/unit/customers/properties-options.test.ts new file mode 100644 index 0000000..a9a69d2 --- /dev/null +++ b/tests/unit/customers/properties-options.test.ts @@ -0,0 +1,115 @@ +import { PlaneClient } from "../../../src/client/plane-client"; +import { CustomerProperty } from "../../../src/models/Customer"; +import { config } from "../constants"; +import { createTestClient, randomizeName } from "../../helpers/test-utils"; +import { describeIf as describe } from "../../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.customerId), "Customer Properties API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let customerId: string; + let customerProperty: CustomerProperty; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + customerId = config.customerId; + }); + + afterAll(async () => { + // Clean up created customer property + if (customerProperty?.id) { + try { + await client.customers.properties.deletePropertyDefinition(workspaceSlug, customerProperty.id); + } catch (error) { + console.warn("Failed to delete customer property:", error); + } + } + }); + + it("should create a customer property", async () => { + const name = randomizeName("test-customer-property"); + customerProperty = await client.customers.properties.createPropertyDefinition(workspaceSlug, { + name: name, + display_name: name, + description: "Test Customer Property Description", + property_type: "TEXT", + settings: { + display_format: "single-line", + }, + }); + + expect(customerProperty).toBeDefined(); + expect(customerProperty.id).toBeDefined(); + expect(customerProperty.name).toBe(name); + expect(customerProperty.description).toBe("Test Customer Property Description"); + }); + + it("should retrieve a customer property", async () => { + const retrievedCustomerProperty = await client.customers.properties.retrievePropertyDefinition( + workspaceSlug, + customerProperty.id! + ); + + expect(retrievedCustomerProperty).toBeDefined(); + expect(retrievedCustomerProperty.id).toBe(customerProperty.id); + expect(retrievedCustomerProperty.name).toBe(customerProperty.name); + }); + + it("should update a customer property", async () => { + const updatedCustomerProperty = await client.customers.properties.updatePropertyDefinition( + workspaceSlug, + customerProperty.id!, + { + display_name: randomizeName("Updated Test Customer Property"), + description: "Updated Test Customer Property Description", + } + ); + + expect(updatedCustomerProperty).toBeDefined(); + expect(updatedCustomerProperty.id).toBe(customerProperty.id); + expect(updatedCustomerProperty.description).toBe("Updated Test Customer Property Description"); + }); + + it("should list customer properties", async () => { + const customerProperties = await client.customers.properties.listPropertyDefinitions(workspaceSlug); + + expect(Array.isArray(customerProperties.results)).toBe(true); + expect(customerProperties.results.length).toBeGreaterThan(0); + + const foundProperty = customerProperties.results.find((p) => p.id === customerProperty.id); + expect(foundProperty).toBeDefined(); + }); + + it("should update a customer property value", async () => { + const customerPropertyValue = await client.customers.properties.updateValue( + workspaceSlug, + customerId, + customerProperty.id!, + { + values: ["Property Value Updated"], + } + ); + + expect(customerPropertyValue).toBeDefined(); + }); + + it("should list customer property values", async () => { + const allCustomerPropertyValues = await client.customers.properties.listValues(workspaceSlug, customerId); + + expect(allCustomerPropertyValues).toBeDefined(); + expect(Object.keys(allCustomerPropertyValues).length).toBeGreaterThan(0); + expect(Array.isArray(allCustomerPropertyValues[customerProperty.id])).toBe(true); + expect(allCustomerPropertyValues[customerProperty.id].length).toBeGreaterThan(0); + }); + + it("should retrieve a customer property value", async () => { + const retrievedCustomerPropertyValue = await client.customers.properties.retrieveValue( + workspaceSlug, + customerId, + customerProperty.id! + ); + + expect(retrievedCustomerPropertyValue).toBeDefined(); + }); +}); diff --git a/tests/unit/customers/requests.test.ts b/tests/unit/customers/requests.test.ts new file mode 100644 index 0000000..7596eb0 --- /dev/null +++ b/tests/unit/customers/requests.test.ts @@ -0,0 +1,79 @@ +import { PlaneClient } from "../../../src/client/plane-client"; +import { CustomerRequest } from "../../../src/models/Customer"; +import { config } from "../constants"; +import { createTestClient, randomizeName } from "../../helpers/test-utils"; +import { describeIf as describe } from "../../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.customerId), "Customer Requests API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let customerId: string; + let customerRequest: CustomerRequest; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + customerId = config.customerId; + }); + + afterAll(async () => { + // Clean up created customer request + if (customerRequest?.id) { + try { + await client.customers.requests.delete(workspaceSlug, customerId, customerRequest.id); + } catch (error) { + console.warn("Failed to delete customer request:", error); + } + } + }); + + it("should create a customer request", async () => { + customerRequest = await client.customers.requests.create(workspaceSlug, customerId, { + name: randomizeName("Test Customer Request"), + description: "Test Customer Request Description", + }); + + expect(customerRequest).toBeDefined(); + expect(customerRequest.id).toBeDefined(); + expect(customerRequest.name).toContain("Test Customer Request"); + expect(customerRequest.description).toBe("Test Customer Request Description"); + }); + + it("should retrieve a customer request", async () => { + const retrievedCustomerRequest = await client.customers.requests.retrieve( + workspaceSlug, + customerId, + customerRequest.id! + ); + + expect(retrievedCustomerRequest).toBeDefined(); + expect(retrievedCustomerRequest.id).toBe(customerRequest.id); + expect(retrievedCustomerRequest.name).toBe(customerRequest.name); + }); + + it("should update a customer request", async () => { + const updatedCustomerRequest = await client.customers.requests.update( + workspaceSlug, + customerId, + customerRequest.id!, + { + name: randomizeName("Updated Test Customer Request"), + description: "Updated Test Customer Request Description", + } + ); + + expect(updatedCustomerRequest).toBeDefined(); + expect(updatedCustomerRequest.id).toBe(customerRequest.id); + expect(updatedCustomerRequest.name).toContain("Updated Test Customer Request"); + expect(updatedCustomerRequest.description).toBe("Updated Test Customer Request Description"); + }); + + it("should list customer requests", async () => { + const customerRequests = await client.customers.requests.list(workspaceSlug, customerId); + + expect(customerRequests.results.length).toBeGreaterThan(0); + + const foundRequest = customerRequests.results.find((r) => r.id === customerRequest.id); + expect(foundRequest).toBeDefined(); + }); +}); diff --git a/tests/unit/customers/work-items.test.ts b/tests/unit/customers/work-items.test.ts new file mode 100644 index 0000000..c995ec0 --- /dev/null +++ b/tests/unit/customers/work-items.test.ts @@ -0,0 +1,38 @@ +import { PlaneClient } from "../../../src/client/plane-client"; +import { config } from "../constants"; +import { createTestClient } from "../../helpers/test-utils"; +import { describeIf as describe } from "../../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.customerId && config.workItemId), "Customer Work Items API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let customerId: string; + let workItemId: string; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + customerId = config.customerId; + workItemId = config.workItemId; + }); + + it("should link work items to customer", async () => { + const linkedIssues = await client.customers.linkIssuesToCustomer(workspaceSlug, customerId, [workItemId]); + + expect(linkedIssues).toBeDefined(); + expect(linkedIssues.linked_issues.length).toBe(1); + }); + + it("should list customer work items", async () => { + const customerWorkItems = await client.customers.listCustomerIssues(workspaceSlug, customerId); + + expect(customerWorkItems.length).toBeGreaterThan(0); + + const foundWorkItem = customerWorkItems.find((wi) => wi.id === workItemId); + expect(foundWorkItem).toBeDefined(); + }); + + it("should unlink work item from customer", async () => { + await client.customers.unlinkIssueFromCustomer(workspaceSlug, customerId, workItemId); + }); +}); diff --git a/tests/unit/cycle.test.ts b/tests/unit/cycle.test.ts new file mode 100644 index 0000000..b75d495 --- /dev/null +++ b/tests/unit/cycle.test.ts @@ -0,0 +1,182 @@ +import { PlaneClient } from "../../src/client/plane-client"; +import { UpdateCycleRequest, Cycle, WorkItem } from "../../src/models"; +import { config } from "./constants"; +import { createTestClient } from "../helpers/test-utils"; +import { describeIf as describe } from "../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.projectId && config.workItemId), "Cycle API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + let workItemId: string; + let userId: string; + let cycle: Cycle; + let cycle2: Cycle; + let workItem2: WorkItem; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + workItemId = config.workItemId; + + // Get current user for cycle ownership + const me = await client.users.me(); + userId = me.id!; + + // Ensure project has cycle view enabled + const project = await client.projects.retrieve(workspaceSlug, projectId); + if (!project.cycle_view) { + await client.projects.update(workspaceSlug, projectId, { + cycle_view: true, + }); + } + }); + + afterAll(async () => { + // Clean up created resources + if (cycle?.id) { + try { + await client.cycles.delete(workspaceSlug, projectId, cycle.id); + } catch (error) { + console.warn("Failed to delete cycle:", error); + } + } + if (cycle2?.id) { + try { + await client.cycles.delete(workspaceSlug, projectId, cycle2.id); + } catch (error) { + console.warn("Failed to delete cycle2:", error); + } + } + if (workItem2?.id) { + try { + await client.workItems.delete(workspaceSlug, projectId, workItem2.id); + } catch (error) { + console.warn("Failed to delete workItem2:", error); + } + } + }); + + it("should create a cycle", async () => { + cycle = await client.cycles.create(workspaceSlug, projectId, { + name: "Test Cycle", + description: "Test Cycle Description", + owned_by: userId, + project_id: projectId, + }); + + expect(cycle).toBeDefined(); + expect(cycle.id).toBeDefined(); + expect(cycle.name).toBe("Test Cycle"); + expect(cycle.description).toBe("Test Cycle Description"); + expect(cycle.owned_by).toBe(userId); + expect(cycle.project).toBe(projectId); + }); + + it("should retrieve a cycle", async () => { + const retrievedCycle = await client.cycles.retrieve(workspaceSlug, projectId, cycle.id); + + expect(retrievedCycle).toBeDefined(); + expect(retrievedCycle.id).toBe(cycle.id); + expect(retrievedCycle.name).toBe(cycle.name); + expect(retrievedCycle.description).toBe(cycle.description); + }); + + it("should update a cycle", async () => { + const updateData: UpdateCycleRequest = { + name: "Updated Test Cycle", + description: "Updated Test Cycle Description", + }; + + const updatedCycle = await client.cycles.update(workspaceSlug, projectId, cycle.id, updateData); + + expect(updatedCycle).toBeDefined(); + expect(updatedCycle.id).toBe(cycle.id); + expect(updatedCycle.name).toBe("Updated Test Cycle"); + expect(updatedCycle.description).toBe("Updated Test Cycle Description"); + }); + + it("should list cycles", async () => { + const cycles = await client.cycles.list(workspaceSlug, projectId); + + expect(cycles).toBeDefined(); + expect(Array.isArray(cycles.results)).toBe(true); + expect(cycles.results.length).toBeGreaterThan(0); + + const foundCycle = cycles.results.find((c) => c.id === cycle.id); + expect(foundCycle).toBeDefined(); + expect(foundCycle?.name).toBe("Updated Test Cycle"); + }); + + it("should add work items to cycle", async () => { + await client.cycles.addWorkItemsToCycle(workspaceSlug, projectId, cycle.id, [workItemId]); + + const itemsInCycle = await client.cycles.listWorkItemsInCycle(workspaceSlug, projectId, cycle.id); + + expect(itemsInCycle).toBeDefined(); + expect(Array.isArray(itemsInCycle.results)).toBe(true); + expect(itemsInCycle.results.length).toBeGreaterThan(0); + + const foundWorkItem = itemsInCycle.results.find((item) => item.id === workItemId); + expect(foundWorkItem).toBeDefined(); + }); + + it("should remove work item from cycle", async () => { + await client.cycles.removeWorkItemFromCycle(workspaceSlug, projectId, cycle.id, workItemId); + + const itemsInCycle = await client.cycles.listWorkItemsInCycle(workspaceSlug, projectId, cycle.id); + + expect(itemsInCycle).toBeDefined(); + expect(Array.isArray(itemsInCycle.results)).toBe(true); + + const foundWorkItem = itemsInCycle.results.find((item) => item.id === workItemId); + expect(foundWorkItem).toBeUndefined(); + }); + + it("should create a second cycle for transfer testing", async () => { + cycle2 = await client.cycles.create(workspaceSlug, projectId, { + name: "Test Cycle 2", + description: "Test Cycle 2 Description", + owned_by: userId, + project_id: projectId, + }); + + expect(cycle2).toBeDefined(); + expect(cycle2.id).toBeDefined(); + expect(cycle2.name).toBe("Test Cycle 2"); + expect(cycle2.id).not.toBe(cycle.id); + }); + + it("should create a new work item for transfer testing", async () => { + workItem2 = await client.workItems.create(workspaceSlug, projectId, { + name: "Test Work Item 2", + }); + + expect(workItem2).toBeDefined(); + expect(workItem2.id).toBeDefined(); + expect(workItem2.name).toBe("Test Work Item 2"); + }); + + it("should transfer work items to another cycle", async () => { + // Add work item back to first cycle for transfer test + await client.cycles.addWorkItemsToCycle(workspaceSlug, projectId, cycle.id, [workItemId]); + + // Transfer work items from first cycle to second cycle + await client.cycles.transferWorkItemsToAnotherCycle(workspaceSlug, projectId, cycle.id, { + new_cycle_id: cycle2.id, + }); + + // Verify work item is now in second cycle + const itemsInCycle2 = await client.cycles.listWorkItemsInCycle(workspaceSlug, projectId, cycle2.id); + expect(itemsInCycle2.results.length).toBeGreaterThan(0); + + const foundWorkItem = itemsInCycle2.results.find((item) => item.id === workItemId); + expect(foundWorkItem).toBeDefined(); + + // Verify work item is no longer in first cycle + const itemsInCycle1 = await client.cycles.listWorkItemsInCycle(workspaceSlug, projectId, cycle.id); + const foundWorkItemInCycle1 = itemsInCycle1.results.find((item) => item.id === workItemId); + expect(foundWorkItemInCycle1).toBeUndefined(); + }); +}); diff --git a/tests/unit/epic.test.ts b/tests/unit/epic.test.ts new file mode 100644 index 0000000..e3b709e --- /dev/null +++ b/tests/unit/epic.test.ts @@ -0,0 +1,30 @@ +import { config } from "./constants"; +import { PlaneClient } from "../../src/client/plane-client"; +import { createTestClient } from "../helpers/test-utils"; +import { describeIf as describe } from "../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.projectId), "Epic API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + }); + + it("should list epics", async () => { + const epics = await client.epics.list(workspaceSlug, projectId); + expect(epics).toBeDefined(); + expect(epics.results.length).toBeGreaterThan(0); + }); + + it("should retrieve an epic", async () => { + const epics = await client.epics.list(workspaceSlug, projectId); + const epic = await client.epics.retrieve(workspaceSlug, projectId, epics.results[0]!.id!); + expect(epic).toBeDefined(); + expect(epic.id).toBe(epics.results[0]!.id); + expect(epic.name).toBe(epics.results[0]!.name); + }); +}); diff --git a/tests/unit/intake.test.ts b/tests/unit/intake.test.ts new file mode 100644 index 0000000..8e0d146 --- /dev/null +++ b/tests/unit/intake.test.ts @@ -0,0 +1,78 @@ +import { PlaneClient } from "../../src/client/plane-client"; +import { IntakeWorkItem } from "../../src/models/Intake"; +import { config } from "./constants"; +import { createTestClient, randomizeName } from "../helpers/test-utils"; +import { describeIf as describe } from "../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.projectId), "Intake API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + let intakeWorkItem: IntakeWorkItem; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + + // Enable intake view if not already enabled + await client.projects.update(workspaceSlug, projectId, { + intake_view: true, + }); + }); + + afterAll(async () => { + // Clean up created intake work item + if (intakeWorkItem?.issue) { + try { + await client.intake.delete(workspaceSlug, projectId, intakeWorkItem.issue); + } catch (error) { + console.warn("Failed to delete intake work item:", error); + } + } + }); + + it("should create an intake work item", async () => { + intakeWorkItem = await client.intake.create(workspaceSlug, projectId, { + issue: { + name: randomizeName("Test Intake"), + description_html: "

Test Intake Description

", + }, + }); + + expect(intakeWorkItem).toBeDefined(); + expect(intakeWorkItem.id).toBeDefined(); + expect(intakeWorkItem.issue).toBeDefined(); + }); + + it("should retrieve an intake work item", async () => { + const retrievedIntake = await client.intake.retrieve(workspaceSlug, projectId, intakeWorkItem.issue!); + + expect(retrievedIntake).toBeDefined(); + expect(retrievedIntake.id).toBe(intakeWorkItem.id); + expect(retrievedIntake.issue).toBe(intakeWorkItem.issue); + }); + + it("should update an intake work item", async () => { + const updatedIntake = await client.intake.update(workspaceSlug, projectId, intakeWorkItem.issue!, { + issue: { + name: "Updated Test Intake", + description_html: "

Updated Test Intake Description

", + }, + }); + + expect(updatedIntake).toBeDefined(); + expect(updatedIntake.id).toBe(intakeWorkItem.id); + }); + + it("should list intake work items", async () => { + const intakes = await client.intake.list(workspaceSlug, projectId); + + expect(intakes).toBeDefined(); + expect(Array.isArray(intakes.results)).toBe(true); + expect(intakes.results.length).toBeGreaterThan(0); + + const foundIntake = intakes.results.find((i) => i.id === intakeWorkItem.id); + expect(foundIntake).toBeDefined(); + }); +}); diff --git a/tests/unit/label.test.ts b/tests/unit/label.test.ts new file mode 100644 index 0000000..d68d664 --- /dev/null +++ b/tests/unit/label.test.ts @@ -0,0 +1,72 @@ +import { PlaneClient } from "../../src/client/plane-client"; +import { Label } from "../../src/models/Label"; +import { config } from "./constants"; +import { createTestClient, randomizeName } from "../helpers/test-utils"; +import { describeIf as describe } from "../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.projectId), "Label API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + let label: Label; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + }); + + afterAll(async () => { + // Clean up created label + if (label?.id) { + try { + await client.labels.delete(workspaceSlug, projectId, label.id); + } catch (error) { + console.warn("Failed to delete label:", error); + } + } + }); + + it("should create a label", async () => { + label = await client.labels.create(workspaceSlug, projectId, { + name: randomizeName("Test Label"), + description: "Test Label Description", + }); + + expect(label).toBeDefined(); + expect(label.id).toBeDefined(); + expect(label.name).toContain("Test Label"); + expect(label.description).toBe("Test Label Description"); + }); + + it("should retrieve a label", async () => { + const retrievedLabel = await client.labels.retrieve(workspaceSlug, projectId, label.id!); + + expect(retrievedLabel).toBeDefined(); + expect(retrievedLabel.id).toBe(label.id); + expect(retrievedLabel.name).toBe(label.name); + expect(retrievedLabel.description).toBe(label.description); + }); + + it("should update a label", async () => { + const updatedLabel = await client.labels.update(workspaceSlug, projectId, label.id!, { + description: "Updated Test Label Description", + }); + + expect(updatedLabel).toBeDefined(); + expect(updatedLabel.id).toBe(label.id); + expect(updatedLabel.description).toBe("Updated Test Label Description"); + }); + + it("should list labels", async () => { + const labels = await client.labels.list(workspaceSlug, projectId); + + expect(labels).toBeDefined(); + expect(Array.isArray(labels.results)).toBe(true); + expect(labels.results.length).toBeGreaterThan(0); + + const foundLabel = labels.results.find((l) => l.id === label.id); + expect(foundLabel).toBeDefined(); + expect(foundLabel?.description).toBe("Updated Test Label Description"); + }); +}); diff --git a/tests/unit/module.test.ts b/tests/unit/module.test.ts new file mode 100644 index 0000000..884a3ce --- /dev/null +++ b/tests/unit/module.test.ts @@ -0,0 +1,99 @@ +import { PlaneClient } from "../../src/client/plane-client"; +import { Module } from "../../src/models/Module"; +import { config } from "./constants"; +import { createTestClient, randomizeName } from "../helpers/test-utils"; +import { describeIf as describe } from "../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.projectId && config.workItemId), "Module API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + let workItemId: string; + let module: Module; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + workItemId = config.workItemId; + }); + + afterAll(async () => { + // Clean up created module + if (module?.id) { + try { + await client.modules.delete(workspaceSlug, projectId, module.id); + } catch (error) { + console.warn("Failed to delete module:", error); + } + } + }); + + it("should create a module", async () => { + module = await client.modules.create(workspaceSlug, projectId, { + name: randomizeName("Test Module"), + description: "Test Description", + }); + + expect(module).toBeDefined(); + expect(module.id).toBeDefined(); + expect(module.name).toContain("Test Module"); + expect(module.description).toBe("Test Description"); + }); + + it("should retrieve a module", async () => { + const retrievedModule = await client.modules.retrieve(workspaceSlug, projectId, module.id!); + + expect(retrievedModule).toBeDefined(); + expect(retrievedModule.id).toBe(module.id); + expect(retrievedModule.name).toBe(module.name); + expect(retrievedModule.description).toBe(module.description); + }); + + it("should update a module", async () => { + const updatedModule = await client.modules.update(workspaceSlug, projectId, module.id!, { + description: "Updated Test Description", + }); + + expect(updatedModule).toBeDefined(); + expect(updatedModule.id).toBe(module.id); + expect(updatedModule.description).toBe("Updated Test Description"); + }); + + it("should list modules", async () => { + const modules = await client.modules.list(workspaceSlug, projectId); + + expect(modules).toBeDefined(); + expect(Array.isArray(modules.results)).toBe(true); + expect(modules.results.length).toBeGreaterThan(0); + + const foundModule = modules.results.find((m) => m.id === module.id); + expect(foundModule).toBeDefined(); + expect(foundModule?.description).toBe("Updated Test Description"); + }); + + it("should add work items to module", async () => { + await client.modules.addWorkItemsToModule(workspaceSlug, projectId, module.id!, [workItemId]); + + const itemsInModule = await client.modules.listWorkItemsInModule(workspaceSlug, projectId, module.id!); + + expect(itemsInModule).toBeDefined(); + expect(Array.isArray(itemsInModule.results)).toBe(true); + expect(itemsInModule.results.length).toBeGreaterThan(0); + + const foundWorkItem = itemsInModule.results.find((item) => item.id === workItemId); + expect(foundWorkItem).toBeDefined(); + }); + + it("should remove work item from module", async () => { + await client.modules.removeWorkItemFromModule(workspaceSlug, projectId, module.id!, workItemId); + + const itemsInModule = await client.modules.listWorkItemsInModule(workspaceSlug, projectId, module.id!); + + expect(itemsInModule).toBeDefined(); + expect(Array.isArray(itemsInModule.results)).toBe(true); + + const foundWorkItem = itemsInModule.results.find((item) => item.id === workItemId); + expect(foundWorkItem).toBeUndefined(); + }); +}); diff --git a/tests/unit/oauth.test.ts b/tests/unit/oauth.test.ts new file mode 100644 index 0000000..5d6ae47 --- /dev/null +++ b/tests/unit/oauth.test.ts @@ -0,0 +1,59 @@ +import { OAuthClient } from "../../src/client/oauth-client"; +import { describeIf as describe } from "../helpers/conditional-tests"; + +const hasOAuthEnv = !!( + process.env.PLANE_BASE_URL && + process.env.PLANE_CLIENT_ID && + process.env.PLANE_CLIENT_SECRET && + process.env.PLANE_REDIRECT_URI && + process.env.PLANE_APP_INSTALLATION_ID +); + +describe(hasOAuthEnv, "OAuth API Tests", () => { + let oauthClient: OAuthClient; + let baseUrl: string; + let clientId: string; + let clientSecret: string; + let redirectUri: string; + let appInstallationId: string; + + beforeAll(() => { + baseUrl = process.env.PLANE_BASE_URL!; + clientId = process.env.PLANE_CLIENT_ID!; + clientSecret = process.env.PLANE_CLIENT_SECRET!; + redirectUri = process.env.PLANE_REDIRECT_URI!; + appInstallationId = process.env.PLANE_APP_INSTALLATION_ID!; + + oauthClient = new OAuthClient({ + baseUrl, + clientId, + clientSecret, + redirectUri, + }); + }); + + it("should generate authorization URL", () => { + const authUrl = oauthClient.getAuthorizationUrl(); + + expect(authUrl).toBeDefined(); + expect(typeof authUrl).toBe("string"); + expect(authUrl).toContain(baseUrl); + expect(authUrl).toContain(clientId); + }); + + it("should get bot token", async () => { + const botToken = await oauthClient.getBotToken(appInstallationId); + + expect(botToken).toBeDefined(); + expect(botToken.access_token).toBeDefined(); + expect(typeof botToken.access_token).toBe("string"); + }); + + it("should get app installations", async () => { + const botToken = await oauthClient.getBotToken(appInstallationId); + const installations = await oauthClient.getAppInstallations(botToken.access_token); + + expect(installations).toBeDefined(); + expect(Array.isArray(installations)).toBe(true); + }); +}); diff --git a/tests/unit/page.test.ts b/tests/unit/page.test.ts new file mode 100644 index 0000000..c8efd8d --- /dev/null +++ b/tests/unit/page.test.ts @@ -0,0 +1,59 @@ +import { PlaneClient } from "../../src/client/plane-client"; +import { Page } from "../../src/models/Page"; +import { config } from "./constants"; +import { createTestClient, randomizeName } from "../helpers/test-utils"; +import { describeIf as describe } from "../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.projectId), "Page API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + let workspacePage: Page; + let projectPage: Page; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + }); + + it("should create a workspace page", async () => { + const content = "

Test Page Content

"; + workspacePage = await client.pages.createWorkspacePage(workspaceSlug, { + name: randomizeName("Test Workspace Page"), + description_html: content, + }); + + expect(workspacePage).toBeDefined(); + expect(workspacePage.id).toBeDefined(); + expect(workspacePage.name).toContain("Test Workspace Page"); + }); + + it("should retrieve a workspace page", async () => { + const retrievedPage = await client.pages.retrieveWorkspacePage(workspaceSlug, workspacePage.id!); + + expect(retrievedPage).toBeDefined(); + expect(retrievedPage.id).toBe(workspacePage.id); + expect(retrievedPage.name).toBe(workspacePage.name); + }); + + it("should create a project page", async () => { + const content = "

Test Project Page Content

"; + projectPage = await client.pages.createProjectPage(workspaceSlug, projectId, { + name: randomizeName("Test Project Page"), + description_html: content, + }); + + expect(projectPage).toBeDefined(); + expect(projectPage.id).toBeDefined(); + expect(projectPage.name).toContain("Test Project Page"); + }); + + it("should retrieve a project page", async () => { + const retrievedProjectPage = await client.pages.retrieveProjectPage(workspaceSlug, projectId, projectPage.id!); + + expect(retrievedProjectPage).toBeDefined(); + expect(retrievedProjectPage.id).toBe(projectPage.id); + expect(retrievedProjectPage.name).toBe(projectPage.name); + }); +}); diff --git a/tests/unit/project.test.ts b/tests/unit/project.test.ts new file mode 100644 index 0000000..e029e35 --- /dev/null +++ b/tests/unit/project.test.ts @@ -0,0 +1,83 @@ +import { PlaneClient } from "../../src/client/plane-client"; +import { UpdateProject, Project } from "../../src/models/Project"; +import { config } from "./constants"; +import { createTestClient, randomizeName } from "../helpers/test-utils"; +import { describeIf as describe } from "../helpers/conditional-tests"; + +describe(!!config.workspaceSlug, "Project API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let project: Project; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + }); + + afterAll(async () => { + // Clean up created project + if (project?.id) { + try { + await client.projects.delete(workspaceSlug, project.id); + } catch (error) { + console.warn("Failed to delete project:", error); + } + } + }); + + it("should create a project", async () => { + const name = randomizeName(); + project = await client.projects.create(workspaceSlug, { + name: name, + identifier: name.slice(0, 5).toUpperCase(), + description: "Test Project Description", + }); + + expect(project).toBeDefined(); + expect(project.id).toBeDefined(); + expect(project.name).toBe(name); + expect(project.description).toBe("Test Project Description"); + }); + + it("should retrieve a project", async () => { + const retrievedProject = await client.projects.retrieve(workspaceSlug, project.id); + + expect(retrievedProject).toBeDefined(); + expect(retrievedProject.id).toBe(project.id); + expect(retrievedProject.name).toBe(project.name); + expect(retrievedProject.description).toBe(project.description); + }); + + it("should update a project", async () => { + const updateData: UpdateProject = { + name: "Updated Test Project", + description: "Updated Test Project Description", + }; + + const updatedProject = await client.projects.update(workspaceSlug, project.id, updateData); + + expect(updatedProject).toBeDefined(); + expect(updatedProject.id).toBe(project.id); + expect(updatedProject.name).toBe("Updated Test Project"); + expect(updatedProject.description).toBe("Updated Test Project Description"); + }); + + it("should list projects", async () => { + const projects = await client.projects.list(workspaceSlug); + + expect(projects).toBeDefined(); + expect(Array.isArray(projects.results)).toBe(true); + expect(projects.results.length).toBeGreaterThan(0); + + const foundProject = projects.results.find((p) => p.id === project.id); + expect(foundProject).toBeDefined(); + expect(foundProject?.name).toBe("Updated Test Project"); + }); + + it("should get project members", async () => { + const members = await client.projects.getMembers(workspaceSlug, project.id); + + expect(members).toBeDefined(); + expect(Array.isArray(members)).toBe(true); + }); +}); diff --git a/tests/unit/state.test.ts b/tests/unit/state.test.ts new file mode 100644 index 0000000..37ff92a --- /dev/null +++ b/tests/unit/state.test.ts @@ -0,0 +1,75 @@ +import { PlaneClient } from "../../src/client/plane-client"; +import { State } from "../../src/models/State"; +import { config } from "./constants"; +import { createTestClient, randomizeName } from "../helpers/test-utils"; +import { describeIf as describe } from "../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.projectId), "State API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + let state: State; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + }); + + afterAll(async () => { + // Clean up created state + if (state?.id) { + try { + await client.states.delete(workspaceSlug, projectId, state.id); + } catch (error) { + console.warn("Failed to delete state:", error); + } + } + }); + + it("should create a state", async () => { + state = await client.states.create(workspaceSlug, projectId, { + name: randomizeName("Test State"), + description: "Test State Description", + group: "started", + color: "#9AA4BC", + }); + + expect(state).toBeDefined(); + expect(state.id).toBeDefined(); + expect(state.name).toContain("Test State"); + expect(state.description).toBe("Test State Description"); + expect(state.group).toBe("started"); + }); + + it("should retrieve a state", async () => { + const retrievedState = await client.states.retrieve(workspaceSlug, projectId, state.id!); + + expect(retrievedState).toBeDefined(); + expect(retrievedState.id).toBe(state.id); + expect(retrievedState.name).toBe(state.name); + expect(retrievedState.description).toBe(state.description); + }); + + it("should update a state", async () => { + const updatedState = await client.states.update(workspaceSlug, projectId, state.id!, { + description: "Updated Test State Description", + }); + + expect(updatedState).toBeDefined(); + expect(updatedState.id).toBe(state.id); + expect(updatedState.description).toBe("Updated Test State Description"); + }); + + it("should list states", async () => { + const states = await client.states.list(workspaceSlug, projectId); + + expect(states).toBeDefined(); + expect(Array.isArray(states.results)).toBe(true); + expect(states.results.length).toBeGreaterThan(0); + + const foundState = states.results.find((s) => s.id === state.id); + expect(foundState).toBeDefined(); + expect(foundState?.description).toBe("Updated Test State Description"); + }); +}); diff --git a/tests/unit/work-item-types/properties-options.test.ts b/tests/unit/work-item-types/properties-options.test.ts new file mode 100644 index 0000000..e3992e3 --- /dev/null +++ b/tests/unit/work-item-types/properties-options.test.ts @@ -0,0 +1,145 @@ +import { PlaneClient } from "../../../src/client/plane-client"; +import { WorkItemProperty, WorkItemPropertyOption } from "../../../src/models/WorkItemProperty"; +import { config } from "../constants"; +import { createTestClient, randomizeName } from "../../helpers/test-utils"; +import { describeIf as describe } from "../../helpers/conditional-tests"; + +describe( + !!(config.workspaceSlug && config.projectId && config.workItemTypeId), + "Work Item Type Properties and Options API Tests", + () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + let workItemTypeId: string; + let textProperty: WorkItemProperty; + let optionProperty: WorkItemProperty; + let propertyOption: WorkItemPropertyOption; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + workItemTypeId = config.workItemTypeId; + + // Enable work item types if not already enabled + await client.projects.update(workspaceSlug, projectId, { + is_issue_type_enabled: true, + }); + }); + + it("should test complete work item type properties and options workflow", async () => { + // ===== TEST TEXT PROPERTY ===== + // Create a TEXT property + const textPropertyName = randomizeName("Test WI Type Property"); + textProperty = await client.workItemProperties.create(workspaceSlug, projectId, workItemTypeId, { + name: textPropertyName, + display_name: textPropertyName, + property_type: "TEXT", + settings: { + display_format: "single-line", + }, + is_required: false, + }); + + expect(textProperty).toBeDefined(); + expect(textProperty.id).toBeDefined(); + expect(textProperty.property_type).toBe("TEXT"); + + // Retrieve the property + const retrievedTextProperty = await client.workItemProperties.retrieve( + workspaceSlug, + projectId, + workItemTypeId, + textProperty.id! + ); + + expect(retrievedTextProperty).toBeDefined(); + expect(retrievedTextProperty.id).toBe(textProperty.id); + + // Update the property + const updatedTextPropertyName = randomizeName("Updated Test WI Type Property"); + const updatedTextProperty = await client.workItemProperties.update( + workspaceSlug, + projectId, + workItemTypeId, + textProperty.id!, + { + name: updatedTextPropertyName, + } + ); + + expect(updatedTextProperty).toBeDefined(); + expect(updatedTextProperty.id).toBe(textProperty.id); + + // List properties + const properties = await client.workItemProperties.list(workspaceSlug, projectId, workItemTypeId, { + limit: 10, + offset: 0, + }); + + expect(properties).toBeDefined(); + expect(Array.isArray(properties)).toBe(true); + const foundProperty = properties.find((p) => p.id === textProperty.id); + expect(foundProperty).toBeDefined(); + + // Delete the TEXT property + await client.workItemProperties.delete(workspaceSlug, projectId, workItemTypeId, textProperty.id!); + + // ===== TEST OPTION PROPERTY AND OPTIONS ===== + // Create an OPTION property + const optionPropertyName = randomizeName("Test Option Property"); + optionProperty = await client.workItemProperties.create(workspaceSlug, projectId, workItemTypeId, { + name: optionPropertyName, + display_name: optionPropertyName, + property_type: "OPTION", + is_required: false, + }); + + expect(optionProperty).toBeDefined(); + expect(optionProperty.id).toBeDefined(); + expect(optionProperty.property_type).toBe("OPTION"); + + // Create a property option + const optionName = randomizeName("Test Property Option"); + propertyOption = await client.workItemProperties.options.create(workspaceSlug, projectId, optionProperty.id!, { + name: optionName, + }); + + expect(propertyOption).toBeDefined(); + expect(propertyOption.id).toBeDefined(); + + // Retrieve the property option + const retrievedOption = await client.workItemProperties.options.retrieve( + workspaceSlug, + projectId, + optionProperty.id!, + propertyOption.id! + ); + + expect(retrievedOption).toBeDefined(); + expect(retrievedOption.id).toBe(propertyOption.id); + + // Update the property option + const updatedOptionName = randomizeName("Updated Property Option"); + const updatedOption = await client.workItemProperties.options.update( + workspaceSlug, + projectId, + optionProperty.id!, + propertyOption.id!, + { + name: updatedOptionName, + } + ); + + expect(updatedOption).toBeDefined(); + expect(updatedOption.id).toBe(propertyOption.id); + + // Delete the property option + await client.workItemProperties.options.delete(workspaceSlug, projectId, optionProperty.id!, propertyOption.id!); + + // Delete the OPTION property + await client.workItemProperties.delete(workspaceSlug, projectId, workItemTypeId, optionProperty.id!); + }); + } +); diff --git a/tests/unit/work-item-types/properties-values.test.ts b/tests/unit/work-item-types/properties-values.test.ts new file mode 100644 index 0000000..b5f4e1a --- /dev/null +++ b/tests/unit/work-item-types/properties-values.test.ts @@ -0,0 +1,63 @@ +import { PlaneClient } from "../../../src/client/plane-client"; +import { config } from "../constants"; +import { createTestClient, randomizeName } from "../../helpers/test-utils"; +import { describeIf as describe } from "../../helpers/conditional-tests"; + +describe( + !!(config.workspaceSlug && config.projectId && config.workItemId && config.customTextPropertyId), + "Work Item Property Values API Tests", + () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + let workItemId: string; + let propertyId: string; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + workItemId = config.workItemId; + propertyId = config.customTextPropertyId; + + // Enable work item types if not already enabled + await client.projects.update(workspaceSlug, projectId, { + is_issue_type_enabled: true, + }); + }); + + it("should test complete work item property values workflow", async () => { + // Create/update a work item property value + const testValue = randomizeName("Test WI Property Value"); + const workItemPropertyValue = await client.workItemProperties.values.create( + workspaceSlug, + projectId, + workItemId, + propertyId, + { + values: [{ value: testValue }], + } + ); + + expect(workItemPropertyValue).toBeDefined(); + + // Retrieve the work item property value + const retrievedWorkItemPropertyValue = await client.workItemProperties.values.retrieve( + workspaceSlug, + projectId, + workItemId, + propertyId + ); + + expect(retrievedWorkItemPropertyValue).toBeDefined(); + + // List all work item property values for the work item + const workItemPropertyValues = await client.workItemProperties.values.list(workspaceSlug, projectId, workItemId, { + limit: 10, + offset: 0, + }); + + expect(workItemPropertyValues).toBeDefined(); + }); + } +); diff --git a/tests/unit/work-item-types/types.test.ts b/tests/unit/work-item-types/types.test.ts new file mode 100644 index 0000000..eef7883 --- /dev/null +++ b/tests/unit/work-item-types/types.test.ts @@ -0,0 +1,73 @@ +import { PlaneClient } from "../../../src/client/plane-client"; +import { WorkItemType } from "../../../src/models/WorkItemType"; +import { config } from "../constants"; +import { createTestClient, randomizeName } from "../../helpers/test-utils"; +import { describeIf as describe } from "../../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.projectId), "Work Item Types API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + let workItemType: WorkItemType; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + + // Enable work item types if not already enabled + await client.projects.update(workspaceSlug, projectId, { + is_issue_type_enabled: true, + }); + }); + + afterAll(async () => { + // Clean up created work item type + if (workItemType?.id) { + try { + await client.workItemTypes.delete(workspaceSlug, projectId, workItemType.id); + } catch (error) { + console.warn("Failed to delete work item type:", error); + } + } + }); + + it("should create a work item type", async () => { + workItemType = await client.workItemTypes.create(workspaceSlug, projectId, { + name: randomizeName("Test WI Type"), + }); + + expect(workItemType).toBeDefined(); + expect(workItemType.id).toBeDefined(); + expect(workItemType.name).toContain("Test WI Type"); + }); + + it("should retrieve a work item type", async () => { + const retrievedWorkItemType = await client.workItemTypes.retrieve(workspaceSlug, projectId, workItemType.id!); + + expect(retrievedWorkItemType).toBeDefined(); + expect(retrievedWorkItemType.id).toBe(workItemType.id); + expect(retrievedWorkItemType.name).toBe(workItemType.name); + }); + + it("should update a work item type", async () => { + const updatedWorkItemType = await client.workItemTypes.update(workspaceSlug, projectId, workItemType.id!, { + name: randomizeName("Updated Test WI Type"), + }); + + expect(updatedWorkItemType).toBeDefined(); + expect(updatedWorkItemType.id).toBe(workItemType.id); + expect(updatedWorkItemType.name).toContain("Updated Test WI Type"); + }); + + it("should list work item types", async () => { + const workItemTypes = await client.workItemTypes.list(workspaceSlug, projectId); + + expect(workItemTypes).toBeDefined(); + expect(Array.isArray(workItemTypes)).toBe(true); + expect(workItemTypes.length).toBeGreaterThan(0); + + const foundType = workItemTypes.find((t) => t.id === workItemType.id); + expect(foundType).toBeDefined(); + }); +}); diff --git a/tests/unit/work-items/activities.test.ts b/tests/unit/work-items/activities.test.ts new file mode 100644 index 0000000..6515f9a --- /dev/null +++ b/tests/unit/work-items/activities.test.ts @@ -0,0 +1,41 @@ +import { PlaneClient } from "../../../src/client/plane-client"; +import { config } from "../constants"; +import { createTestClient } from "../../helpers/test-utils"; +import { describeIf as describe } from "../../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.projectId && config.workItemId), "Work Item Activities API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + let workItemId: string; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + workItemId = config.workItemId; + }); + + it("should list activities", async () => { + const activityResponse = await client.workItems.activities.list(workspaceSlug, projectId, workItemId); + + expect(activityResponse).toBeDefined(); + expect(Array.isArray(activityResponse.results)).toBe(true); + }); + + it("should retrieve an activity", async () => { + const activityResponse = await client.workItems.activities.list(workspaceSlug, projectId, workItemId); + + if (activityResponse.results.length > 0) { + const activity = await client.workItems.activities.retrieve( + workspaceSlug, + projectId, + workItemId, + activityResponse.results[0]!.id! + ); + + expect(activity).toBeDefined(); + expect(activity.id).toBe(activityResponse.results[0]!.id); + } + }); +}); diff --git a/tests/unit/work-items/comments.test.ts b/tests/unit/work-items/comments.test.ts new file mode 100644 index 0000000..a22ae8e --- /dev/null +++ b/tests/unit/work-items/comments.test.ts @@ -0,0 +1,75 @@ +import { PlaneClient } from "../../../src/client/plane-client"; +import { WorkItemComment } from "../../../src/models/Comment"; +import { config } from "../constants"; +import { createTestClient } from "../../helpers/test-utils"; +import { describeIf as describe } from "../../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.projectId && config.workItemId), "Work Item Comments API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + let workItemId: string; + let comment: WorkItemComment; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + workItemId = config.workItemId; + }); + + afterAll(async () => { + // Clean up created comment + if (comment?.id) { + try { + await client.workItems.comments.delete(workspaceSlug, projectId, workItemId, comment.id); + } catch (error) { + console.warn("Failed to delete comment:", error); + } + } + }); + + it("should create a comment", async () => { + comment = await client.workItems.comments.create(workspaceSlug, projectId, workItemId, { + comment_html: "

Test Comment

", + }); + + expect(comment).toBeDefined(); + expect(comment.id).toBeDefined(); + expect(comment.comment_html).toBe("

Test Comment

"); + }); + + it("should retrieve a comment", async () => { + const retrievedComment = await client.workItems.comments.retrieve( + workspaceSlug, + projectId, + workItemId, + comment.id! + ); + + expect(retrievedComment).toBeDefined(); + expect(retrievedComment.id).toBe(comment.id); + expect(retrievedComment.comment_html).toBe(comment.comment_html); + }); + + it("should update a comment", async () => { + const updatedComment = await client.workItems.comments.update(workspaceSlug, projectId, workItemId, comment.id!, { + comment_html: "

Updated Test Comment

", + }); + + expect(updatedComment).toBeDefined(); + expect(updatedComment.id).toBe(comment.id); + expect(updatedComment.comment_html).toBe("

Updated Test Comment

"); + }); + + it("should list comments", async () => { + const comments = await client.workItems.comments.list(workspaceSlug, projectId, workItemId); + + expect(comments).toBeDefined(); + expect(Array.isArray(comments.results)).toBe(true); + expect(comments.results.length).toBeGreaterThan(0); + + const foundComment = comments.results.find((c) => c.id === comment.id); + expect(foundComment).toBeDefined(); + }); +}); diff --git a/tests/unit/work-items/links.test.ts b/tests/unit/work-items/links.test.ts new file mode 100644 index 0000000..87c4bc4 --- /dev/null +++ b/tests/unit/work-items/links.test.ts @@ -0,0 +1,75 @@ +import { PlaneClient } from "../../../src/client/plane-client"; +import { Link } from "../../../src/models/Link"; +import { config } from "../constants"; +import { createTestClient, randomizeName } from "../../helpers/test-utils"; +import { describeIf as describe } from "../../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.projectId && config.workItemId), "Work Item Links API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + let workItemId: string; + let link: Link; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + workItemId = config.workItemId; + }); + + afterAll(async () => { + // Clean up created link + if (link?.id) { + try { + await client.workItems.links.delete(workspaceSlug, projectId, workItemId, link.id); + } catch (error) { + console.warn("Failed to delete link:", error); + } + } + }); + + it("should create a link", async () => { + link = await client.workItems.links.create(workspaceSlug, projectId, workItemId, { + title: randomizeName("Test Link"), + url: "https://test.com", + }); + + expect(link).toBeDefined(); + expect(link.id).toBeDefined(); + expect(link.title).toContain("Test Link"); + expect(link.url).toBe("https://test.com"); + }); + + it("should retrieve a link", async () => { + const retrievedLink = await client.workItems.links.retrieve(workspaceSlug, projectId, workItemId, link.id!); + + expect(retrievedLink).toBeDefined(); + expect(retrievedLink.id).toBe(link.id); + expect(retrievedLink.title).toBe(link.title); + }); + + it("should update a link", async () => { + const updatedLink = await client.workItems.links.update(workspaceSlug, projectId, workItemId, link.id!, { + title: randomizeName("Updated Test Link"), + url: "https://updated.com", + }); + + expect(updatedLink).toBeDefined(); + expect(updatedLink.id).toBe(link.id); + expect(updatedLink.title).toContain("Updated Test Link"); + expect(updatedLink.url).toBe("https://updated.com"); + }); + + it("should list links", async () => { + const linksResponse = await client.workItems.links.list(workspaceSlug, projectId, workItemId); + + expect(linksResponse).toBeDefined(); + expect(linksResponse.results).toBeDefined(); + expect(Array.isArray(linksResponse.results)).toBe(true); + expect(linksResponse.results.length).toBeGreaterThan(0); + + const foundLink = linksResponse.results.find((l) => l.id === link.id); + expect(foundLink).toBeDefined(); + }); +}); diff --git a/tests/unit/work-items/relations.test.ts b/tests/unit/work-items/relations.test.ts new file mode 100644 index 0000000..d5549fd --- /dev/null +++ b/tests/unit/work-items/relations.test.ts @@ -0,0 +1,54 @@ +import { PlaneClient } from "../../../src/client/plane-client"; +import { config } from "../constants"; +import { createTestClient } from "../../helpers/test-utils"; +import { describeIf as describe } from "../../helpers/conditional-tests"; +import { WorkItemRelationResponse } from "../../../src/models/WorkItemRelation"; +describe( + !!(config.workspaceSlug && config.projectId && config.workItemId && config.workItemId2), + "Work Item Relations API Tests", + () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + let workItemId: string; + let workItemId2: string; + let relatedWorkItemId: string; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + workItemId = config.workItemId; + workItemId2 = config.workItemId2; + + // Get the actual work item ID from the identifier + const workItem2 = await client.workItems.retrieveByIdentifier(workspaceSlug, workItemId2); + relatedWorkItemId = workItem2.id!; + }); + + it("should create a relation", async () => { + const relationData = await client.workItems.relations.create(workspaceSlug, projectId, workItemId, { + relation_type: "blocking", + issues: [relatedWorkItemId], + }); + + expect(relationData).toBeDefined(); + }); + + it("should list relations", async () => { + const relations = await client.workItems.relations.list(workspaceSlug, projectId, workItemId); + + expect(relations).toBeDefined(); + expect(relations.blocking).toContain(relatedWorkItemId); + }); + + it("should delete a relation", async () => { + await client.workItems.relations.delete(workspaceSlug, projectId, workItemId, { + related_issue: relatedWorkItemId, + }); + + const relations = await client.workItems.relations.list(workspaceSlug, projectId, workItemId); + expect(relations.blocking).not.toContain(relatedWorkItemId); + }); + } +); diff --git a/tests/unit/work-items/work-items.test.ts b/tests/unit/work-items/work-items.test.ts new file mode 100644 index 0000000..6d4f5b9 --- /dev/null +++ b/tests/unit/work-items/work-items.test.ts @@ -0,0 +1,106 @@ +import { PlaneClient } from "../../../src/client/plane-client"; +import { WorkItem } from "../../../src/models/WorkItem"; +import { config } from "../constants"; +import { createTestClient, randomizeName } from "../../helpers/test-utils"; +import { describeIf as describe } from "../../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.projectId && config.userId), "Work Items API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + let userId: string; + let workItem: WorkItem; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + userId = config.userId; + }); + + afterAll(async () => { + // Clean up created work item + if (workItem?.id) { + try { + await client.workItems.delete(workspaceSlug, projectId, workItem.id); + } catch (error) { + console.warn("Failed to delete work item:", error); + } + } + }); + + it("should create a work item", async () => { + const name = randomizeName(); + workItem = await client.workItems.create(workspaceSlug, projectId, { + name, + description_html: "

A work item created via the Plane SDK

", + }); + + expect(workItem).toBeDefined(); + expect(workItem.id).toBeDefined(); + expect(workItem.name).toContain(name); + expect(workItem.description_html).toBe("

A work item created via the Plane SDK

"); + }); + + it("should retrieve a work item", async () => { + const retrievedWorkItem = await client.workItems.retrieve(workspaceSlug, projectId, workItem.id!); + + expect(retrievedWorkItem).toBeDefined(); + expect(retrievedWorkItem.id).toBe(workItem.id); + expect(retrievedWorkItem.name).toBe(workItem.name); + }); + + it("should update a work item", async () => { + const states = await client.states.list(workspaceSlug, projectId); + const labels = await client.labels.list(workspaceSlug, projectId); + + const label = labels.results[0]; + const state = states.results[0]; + + const updatedWorkItem = await client.workItems.update(workspaceSlug, projectId, workItem.id!, { + name: `${workItem.name} - Updated`, + description_html: "

Updated Test Work Item Description

", + state: state ? state.id : undefined, + assignees: [userId], + labels: label ? [label.id] : undefined, + }); + + expect(updatedWorkItem).toBeDefined(); + expect(updatedWorkItem.id).toBe(workItem.id); + expect(updatedWorkItem.name).toBe(`${workItem.name} - Updated`); + expect(updatedWorkItem.description_html).toBe("

Updated Test Work Item Description

"); + }); + + it("should list work items", async () => { + const workItems = await client.workItems.list(workspaceSlug, projectId); + + expect(workItems).toBeDefined(); + expect(Array.isArray(workItems.results)).toBe(true); + expect(workItems.results.length).toBeGreaterThan(0); + + const foundWorkItem = workItems.results.find((wi) => wi.id === workItem.id); + expect(foundWorkItem).toBeDefined(); + }); + + it("should retrieve work item by identifier", async () => { + const project = await client.projects.retrieve(workspaceSlug, projectId); + const workItemByIdentifier = await client.workItems.retrieveByIdentifier( + workspaceSlug, + `${project.identifier}-${workItem.sequence_id}`, + ["project"] + ); + + expect(workItemByIdentifier).toBeDefined(); + expect(workItemByIdentifier.id).toBe(workItem.id); + }); + + it("should search work items", async () => { + const searchedWorkItemsResponse = await client.workItems.search(workspaceSlug, workItem.name); + + expect(searchedWorkItemsResponse.issues).toBeDefined(); + expect(Array.isArray(searchedWorkItemsResponse.issues)).toBe(true); + expect(searchedWorkItemsResponse.issues.length).toBeGreaterThan(0); + const foundWorkItem = searchedWorkItemsResponse.issues.find((wi) => wi.id === workItem.id); + expect(foundWorkItem).toBeDefined(); + }); +}); diff --git a/tests/unit/work-items/work-logs.test.ts b/tests/unit/work-items/work-logs.test.ts new file mode 100644 index 0000000..788f1d1 --- /dev/null +++ b/tests/unit/work-items/work-logs.test.ts @@ -0,0 +1,69 @@ +import { PlaneClient } from "../../../src/client/plane-client"; +import { WorkLog } from "../../../src/models/WorkLog"; +import { config } from "../constants"; +import { createTestClient } from "../../helpers/test-utils"; +import { describeIf as describe } from "../../helpers/conditional-tests"; + +describe(!!(config.workspaceSlug && config.projectId && config.workItemId), "Work Logs API Tests", () => { + let client: PlaneClient; + let workspaceSlug: string; + let projectId: string; + let workItemId: string; + let workLog: WorkLog; + + beforeAll(async () => { + client = createTestClient(); + workspaceSlug = config.workspaceSlug; + projectId = config.projectId; + workItemId = config.workItemId; + + // Enable time tracking + await client.projects.update(workspaceSlug, projectId, { + is_time_tracking_enabled: true, + }); + }); + + afterAll(async () => { + // Clean up created work log + if (workLog?.id) { + try { + await client.workItems.workLogs.delete(workspaceSlug, projectId, workItemId, workLog.id); + } catch (error) { + console.warn("Failed to delete work log:", error); + } + } + }); + + it("should create a work log", async () => { + workLog = await client.workItems.workLogs.create(workspaceSlug, projectId, workItemId, { + duration: 30, + description: "Test work log", + }); + + expect(workLog).toBeDefined(); + expect(workLog.id).toBeDefined(); + expect(workLog.duration).toBe(30); + expect(workLog.description).toBe("Test work log"); + }); + + it("should list work logs", async () => { + const workLogs = await client.workItems.workLogs.list(workspaceSlug, projectId, workItemId); + + expect(workLogs).toBeDefined(); + expect(Array.isArray(workLogs)).toBe(true); + expect(workLogs.length).toBeGreaterThan(0); + + const foundWorkLog = workLogs.find((wl) => wl.id === workLog.id); + expect(foundWorkLog).toBeDefined(); + }); + + it("should update a work log", async () => { + const updatedWorkLog = await client.workItems.workLogs.update(workspaceSlug, projectId, workItemId, workLog.id!, { + description: "Updated test work log", + }); + + expect(updatedWorkLog).toBeDefined(); + expect(updatedWorkLog.id).toBe(workLog.id); + expect(updatedWorkLog.description).toBe("Updated test work log"); + }); +}); diff --git a/tests/work-item-types/properties-options.test.ts b/tests/work-item-types/properties-options.test.ts deleted file mode 100644 index 20b5a24..0000000 --- a/tests/work-item-types/properties-options.test.ts +++ /dev/null @@ -1,325 +0,0 @@ -import { PlaneClient } from "../../src/client/plane-client"; -import { PropertyType } from "../../src/models/common"; -import { - WorkItemProperty, - WorkItemPropertyOption, - WorkItemPropertySettings, -} from "../../src/models/WorkItemProperty"; -import { config } from "../constants"; -import { createTestClient } from "../test-utils"; - -export async function testWorkItemTypesPropertiesAndOptions() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const projectId = config.projectId; - const workItemTypeId = config.workItemTypeId; - - if (!workItemTypeId) { - throw new Error( - "workItemTypeId is required to test work item properties and options" - ); - } - - // enable work item types if didn't already - await client.projects.update(workspaceSlug, projectId, { - is_issue_type_enabled: true, - }); - - // test the work item type properties - const workItemTypeProperty = await createWorkItemTypeProperty( - client, - workspaceSlug, - projectId, - workItemTypeId, - "TEXT", - { - display_format: "single-line", - } - ); - console.log("Created work item type property: ", workItemTypeProperty); - - const retrievedWorkItemTypeProperty = await retrieveWorkItemTypeProperty( - client, - workspaceSlug, - projectId, - workItemTypeId, - workItemTypeProperty.id - ); - console.log( - "Retrieved work item type property: ", - retrievedWorkItemTypeProperty - ); - - const updatedWorkItemTypeProperty = await updateWorkItemTypeProperty( - client, - workspaceSlug, - projectId, - workItemTypeId, - workItemTypeProperty.id - ); - console.log("Updated work item type property: ", updatedWorkItemTypeProperty); - - const workItemTypeProperties = await listWorkItemTypeProperties( - client, - workspaceSlug, - projectId, - workItemTypeId - ); - console.log("Listed work item type properties: ", workItemTypeProperties); - - await deleteWorkItemTypeProperty( - client, - workspaceSlug, - projectId, - workItemTypeId, - workItemTypeProperty.id - ); - console.log("Work item type property deleted: ", workItemTypeProperty.id); - - // test the work item type property options - // create a work item type property with property type OPTION - // use the work item type property id to create a work item property option - - const optionWorkItemTypeProperty = await createWorkItemTypeProperty( - client, - workspaceSlug, - projectId, - workItemTypeId, - "OPTION" - ); - console.log( - "Created option work item type property: ", - optionWorkItemTypeProperty - ); - - if (!optionWorkItemTypeProperty.id) { - throw new Error( - "optionWorkItemTypeProperty ID is required to test work item property options" - ); - } - - const createdWorkItemPropertyOption = await createWorkItemPropertyOption( - client, - workspaceSlug, - projectId, - optionWorkItemTypeProperty.id - ); - console.log( - "Created work item property option: ", - createdWorkItemPropertyOption - ); - - const retrievedWorkItemPropertyOption = await retrieveWorkItemPropertyOption( - client, - workspaceSlug, - projectId, - optionWorkItemTypeProperty.id, - createdWorkItemPropertyOption.id - ); - console.log( - "Retrieved work item property option: ", - retrievedWorkItemPropertyOption - ); - - const updatedWorkItemPropertyOption = await updateWorkItemPropertyOption( - client, - workspaceSlug, - projectId, - optionWorkItemTypeProperty.id, - createdWorkItemPropertyOption.id - ); - console.log( - "Updated work item property option: ", - updatedWorkItemPropertyOption - ); - - await deleteWorkItemPropertyOption( - client, - workspaceSlug, - projectId, - optionWorkItemTypeProperty.id, - createdWorkItemPropertyOption.id - ); - console.log( - "Work item property option deleted: ", - createdWorkItemPropertyOption.id - ); - - await deleteWorkItemTypeProperty( - client, - workspaceSlug, - projectId, - workItemTypeId, - optionWorkItemTypeProperty.id - ); - console.log( - "Option work item type property deleted: ", - optionWorkItemTypeProperty.id - ); -} - -async function createWorkItemTypeProperty( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemTypeId: string, - propertyType: PropertyType, - settings?: WorkItemPropertySettings -): Promise { - const workItemTypeProperty = await client.workItemProperties.create( - workspaceSlug, - projectId, - workItemTypeId, - { - name: `Test WI Type Property ${new Date().getTime()}`, - display_name: `Test WI Type Property ${new Date().getTime()}`, - property_type: propertyType, - settings, - is_required: false, - } - ); - return workItemTypeProperty; -} - -async function retrieveWorkItemTypeProperty( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemTypeId: string, - workItemPropertyId: string -): Promise { - const workItemTypeProperty = await client.workItemProperties.retrieve( - workspaceSlug, - projectId, - workItemTypeId, - workItemPropertyId - ); - return workItemTypeProperty; -} - -async function listWorkItemTypeProperties( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemTypeId: string -): Promise { - const workItemTypeProperties = await client.workItemProperties.list( - workspaceSlug, - projectId, - workItemTypeId, - { - limit: 10, - offset: 0, - } - ); - return workItemTypeProperties; -} - -async function updateWorkItemTypeProperty( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemTypeId: string, - workItemPropertyId: string -): Promise { - const updatedWorkItemTypeProperty = await client.workItemProperties.update( - workspaceSlug, - projectId, - workItemTypeId, - workItemPropertyId, - { - name: `Updated Test WI Type Property ${new Date().getTime()}`, - } - ); - return updatedWorkItemTypeProperty; -} - -async function deleteWorkItemTypeProperty( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemTypeId: string, - workItemPropertyId: string -): Promise { - await client.workItemProperties.delete( - workspaceSlug, - projectId, - workItemTypeId, - workItemPropertyId - ); -} - -async function createWorkItemPropertyOption( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemPropertyId: string -): Promise { - const workItemTypePropertyOption = - await client.workItemProperties.options.create( - workspaceSlug, - projectId, - workItemPropertyId, - { - name: `Test Property Option ${new Date().getTime()}`, - } - ); - return workItemTypePropertyOption; -} - -async function retrieveWorkItemPropertyOption( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemPropertyId: string, - workItemPropertyOptionId: string -): Promise { - const workItemTypePropertyOption = - await client.workItemProperties.options.retrieve( - workspaceSlug, - projectId, - workItemPropertyId, - workItemPropertyOptionId - ); - return workItemTypePropertyOption; -} - -async function updateWorkItemPropertyOption( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemPropertyId: string, - workItemPropertyOptionId: string -): Promise { - const workItemTypePropertyOption = - await client.workItemProperties.options.update( - workspaceSlug, - projectId, - workItemPropertyId, - workItemPropertyOptionId, - { - name: `Updated Property Option ${new Date().getTime()}`, - } - ); - return workItemTypePropertyOption; -} - -async function deleteWorkItemPropertyOption( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemPropertyId: string, - workItemPropertyOptionId: string -): Promise { - await client.workItemProperties.options.delete( - workspaceSlug, - projectId, - workItemPropertyId, - workItemPropertyOptionId - ); -} - -if (require.main === module) { - testWorkItemTypesPropertiesAndOptions().catch(console.error); -} diff --git a/tests/work-item-types/properties-values.test.ts b/tests/work-item-types/properties-values.test.ts deleted file mode 100644 index 2b552de..0000000 --- a/tests/work-item-types/properties-values.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { PlaneClient } from "../../src/client/plane-client"; -import { WorkItemPropertyValues } from "../../src/models/WorkItemProperty"; -import { config } from "../constants"; -import { createTestClient } from "../test-utils"; - -export async function testWorkItemPropertiesValues() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const projectId = config.projectId; - const workItemId = config.workItemId; - const propertyId = config.customTextPropertyId; - - // enable work item types if didn't already - await client.projects.update(workspaceSlug, projectId, { - is_issue_type_enabled: true, - }); - - if (!workItemId || !propertyId) { - throw new Error( - "workItemId and propertyId are required to test work item properties and values" - ); - } - - const workItemPropertyValue = await updateWorkItemPropertyValue( - client, - workspaceSlug, - projectId, - workItemId, - propertyId - ); - console.log("Created work item property value: ", workItemPropertyValue); - - const retrievedWorkItemPropertyValue = await retrieveWorkItemPropertyValue( - client, - workspaceSlug, - projectId, - workItemId, - propertyId - ); - console.log( - "Retrieved work item property value: ", - retrievedWorkItemPropertyValue - ); - - const workItemPropertyValues = await listWorkItemPropertyValues( - client, - workspaceSlug, - projectId, - workItemId - ); - console.log("Listed work item property values: ", workItemPropertyValues); -} - -async function updateWorkItemPropertyValue( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string, - propertyId: string -): Promise { - const workItemPropertyValue = await client.workItemProperties.values.create( - workspaceSlug, - projectId, - workItemId, - propertyId, - { - values: [{ value: `Test WI Property Value ${new Date().getTime()}` }], - } - ); - return workItemPropertyValue; -} - -async function retrieveWorkItemPropertyValue( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string, - propertyId: string -): Promise { - const workItemPropertyValue = await client.workItemProperties.values.retrieve( - workspaceSlug, - projectId, - workItemId, - propertyId - ); - return workItemPropertyValue; -} - -async function listWorkItemPropertyValues( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string -): Promise { - const workItemPropertyValues = await client.workItemProperties.values.list( - workspaceSlug, - projectId, - workItemId, - { - limit: 10, - offset: 0, - } - ); - return workItemPropertyValues; -} - -if (require.main === module) { - testWorkItemPropertiesValues().catch(console.error); -} diff --git a/tests/work-item-types/types.test.ts b/tests/work-item-types/types.test.ts deleted file mode 100644 index 80f0567..0000000 --- a/tests/work-item-types/types.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { PlaneClient } from "../../src/client/plane-client"; -import { PaginatedResponse } from "../../src/models/common"; -import { WorkItemType } from "../../src/models/WorkItemType"; -import { config } from "../constants"; -import { createTestClient } from "../test-utils"; - -export async function testWorkItemTypes() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const projectId = config.projectId; - - // enable work item types if didn't already - await client.projects.update(workspaceSlug, projectId, { - is_issue_type_enabled: true, - }); - - const workItemType = await createWorkItemType( - client, - workspaceSlug, - projectId - ); - console.log("Created work item type: ", workItemType); - - const retrievedWorkItemType = await retrieveWorkItemType( - client, - workspaceSlug, - projectId, - workItemType.id - ); - console.log("Retrieved work item type: ", retrievedWorkItemType); - - const updatedWorkItemType = await updateWorkItemType( - client, - workspaceSlug, - projectId, - workItemType.id - ); - console.log("Updated work item type: ", updatedWorkItemType); - - const workItemTypes = await listWorkItemTypes( - client, - workspaceSlug, - projectId - ); - console.log("Listed work item types: ", workItemTypes); - - await deleteWorkItemType(client, workspaceSlug, projectId, workItemType.id); - console.log("Work item type deleted: ", workItemType.id); -} - -async function createWorkItemType( - client: PlaneClient, - workspaceSlug: string, - projectId: string -): Promise { - const workItemType = await client.workItemTypes.create( - workspaceSlug, - projectId, - { - name: `Test WI Type ${new Date().getTime()}`, - } - ); - return workItemType; -} - -async function retrieveWorkItemType( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemTypeId: string -): Promise { - const workItemType = await client.workItemTypes.retrieve( - workspaceSlug, - projectId, - workItemTypeId - ); - return workItemType; -} - -async function listWorkItemTypes( - client: PlaneClient, - workspaceSlug: string, - projectId: string -): Promise> { - const workItemTypes = await client.workItemTypes.list( - workspaceSlug, - projectId, - { - limit: 10, - offset: 0, - } - ); - return workItemTypes; -} - -async function updateWorkItemType( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemTypeId: string -): Promise { - const updatedWorkItemType = await client.workItemTypes.update( - workspaceSlug, - projectId, - workItemTypeId, - { - name: `Updated Test WI Type ${new Date().getTime()}`, - } - ); - return updatedWorkItemType; -} - -async function deleteWorkItemType( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemTypeId: string -): Promise { - await client.workItemTypes.delete(workspaceSlug, projectId, workItemTypeId); -} - -if (require.main === module) { - testWorkItemTypes().catch(console.error); -} diff --git a/tests/work-items/activities.test.ts b/tests/work-items/activities.test.ts deleted file mode 100644 index bf08a84..0000000 --- a/tests/work-items/activities.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { PlaneClient } from "../../src/client/plane-client"; -import { config } from "../constants"; -import { createTestClient } from "../test-utils"; - -export async function testActivities() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const projectId = config.projectId; - const workItemId = config.workItemId; - - const activityResponse = await listActivities( - client, - workspaceSlug, - projectId, - workItemId - ); - console.log("activities list", activityResponse); - - const activity = await retrieveActivity( - client, - workspaceSlug, - projectId, - workItemId, - activityResponse.results[0].id - ); - console.log("activity retrieve", activity); -} - -async function listActivities( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string -) { - const activities = await client.workItems.activities.list( - workspaceSlug, - projectId, - workItemId - ); - return activities; -} - -async function retrieveActivity( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string, - activityId: string -) { - const activity = await client.workItems.activities.retrieve( - workspaceSlug, - projectId, - workItemId, - activityId - ); - return activity; -} - -if (require.main === module) { - testActivities().catch(console.error); -} diff --git a/tests/work-items/comments.test.ts b/tests/work-items/comments.test.ts deleted file mode 100644 index f808247..0000000 --- a/tests/work-items/comments.test.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { PlaneClient } from "../../src/client/plane-client"; -import { WorkItemCommentUpdateRequest } from "../../src/models/Comment"; -import { config } from "../constants"; -import { createTestClient } from "../test-utils"; - -export async function testComments() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const projectId = config.projectId; - const workItemId = config.workItemId; - - const comment = await createComment( - client, - workspaceSlug, - projectId, - workItemId - ); - console.log("Created comment: ", comment); - - const retrievedComment = await retrieveComment( - client, - workspaceSlug, - projectId, - workItemId, - comment.id - ); - console.log("Retrieved comment: ", retrievedComment); - - const updatedComment = await updateComment( - client, - workspaceSlug, - projectId, - workItemId, - comment.id - ); - console.log("Updated comment: ", updatedComment); - - const comments = await listComments( - client, - workspaceSlug, - projectId, - workItemId - ); - console.log("Listed comments: ", comments); - - await deleteComment(client, workspaceSlug, projectId, workItemId, comment.id); - console.log("Deleted comment: ", comment.id); -} - -async function createComment( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string -) { - const comment = await client.workItems.comments.create( - workspaceSlug, - projectId, - workItemId, - { - comment_html: "

Test Comment

", - } - ); - return comment; -} - -async function retrieveComment( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string, - commentId: string -) { - const comment = await client.workItems.comments.retrieve( - workspaceSlug, - projectId, - workItemId, - commentId - ); - return comment; -} - -async function updateComment( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string, - commentId: string -) { - return await client.workItems.comments.update( - workspaceSlug, - projectId, - workItemId, - commentId, - { - comment_html: "

Updated Test Comment

", - } - ); -} - -async function listComments( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string -) { - const comments = await client.workItems.comments.list( - workspaceSlug, - projectId, - workItemId - ); - return comments; -} - -async function deleteComment( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string, - commentId: string -) { - await client.workItems.comments.delete( - workspaceSlug, - projectId, - workItemId, - commentId - ); -} - -if (require.main === module) { - testComments().catch(console.error); -} diff --git a/tests/work-items/links.test.ts b/tests/work-items/links.test.ts deleted file mode 100644 index ada1994..0000000 --- a/tests/work-items/links.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { PlaneClient } from "../../src/client/plane-client"; -import { Link } from "../../src/models/Link"; -import { config } from "../constants"; -import { createTestClient } from "../test-utils"; - -export async function testLinks() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const projectId = config.projectId; - const workItemId = config.workItemId; - - const link = await createLink(client, workspaceSlug, projectId, workItemId); - console.log("Created link: ", link); - - const retrievedLink = await retrieveLink( - client, - workspaceSlug, - projectId, - workItemId, - link.id - ); - console.log("Retrieved link: ", retrievedLink); - - const updatedLink = await updateLink( - client, - workspaceSlug, - projectId, - workItemId, - link.id - ); - console.log("Updated link: ", updatedLink); - - const links = await listLinks(client, workspaceSlug, projectId, workItemId); - console.log("Listed links: ", links); - - await deleteLink(client, workspaceSlug, projectId, workItemId, link.id); - console.log("Deleted link: ", link.id); -} - -async function createLink( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string -) { - const link = await client.workItems.links.create( - workspaceSlug, - projectId, - workItemId, - { - title: "Test Link", - url: "https://test.com", - } - ); - return link; -} - -async function retrieveLink( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string, - linkId: string -) { - const link = await client.workItems.links.retrieve( - workspaceSlug, - projectId, - workItemId, - linkId - ); - return link; -} - -async function updateLink( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string, - linkId: string -) { - return await client.workItems.links.update( - workspaceSlug, - projectId, - workItemId, - linkId, - { - title: "Updated Test Link", - url: "https://updated.com", - } - ); -} - -async function listLinks( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string -) { - const links = await client.workItems.links.list( - workspaceSlug, - projectId, - workItemId - ); - return links; -} - -async function deleteLink( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string, - linkId: string -) { - await client.workItems.links.delete( - workspaceSlug, - projectId, - workItemId, - linkId - ); -} - -if (require.main === module) { - testLinks().catch(console.error); -} diff --git a/tests/work-items/relations.test.ts b/tests/work-items/relations.test.ts deleted file mode 100644 index 4095341..0000000 --- a/tests/work-items/relations.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { PlaneClient } from "../../src/client/plane-client"; -import { - WorkItemRelationCreateRequest, - WorkItemRelationRemoveRequest, -} from "../../src/models/WorkItemRelation"; -import { config } from "../constants"; -import { createTestClient } from "../test-utils"; - -export async function testRelations() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const projectId = config.projectId; - const workItemId = config.workItemId; - - const workItem2 = await client.workItems.retrieveByIdentifier( - workspaceSlug, - config.workItemId2 - ); - - const relationData = await createRelation( - client, - workspaceSlug, - projectId, - workItemId, - { - relation_type: "blocking", - issues: [workItem2.id], - } - ); - console.log("Created relation: ", relationData); - - const relations = await listRelations( - client, - workspaceSlug, - projectId, - workItemId - ); - console.log("Listed relations: ", relations); - - await deleteRelation(client, workspaceSlug, projectId, workItemId, { - related_issue: workItem2.id, - }); - console.log("Deleted relation: ", workItem2.id); -} - -async function createRelation( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string, - relationData: WorkItemRelationCreateRequest -) { - return client.workItems.relations.create( - workspaceSlug, - projectId, - workItemId, - relationData - ); -} - -async function listRelations( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string -) { - return client.workItems.relations.list(workspaceSlug, projectId, workItemId); -} - -async function deleteRelation( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string, - relationData: WorkItemRelationRemoveRequest -) { - return client.workItems.relations.delete( - workspaceSlug, - projectId, - workItemId, - relationData - ); -} - -if (require.main === module) { - testRelations().catch(console.error); -} diff --git a/tests/work-items/work-items.test.ts b/tests/work-items/work-items.test.ts deleted file mode 100644 index de6bcf1..0000000 --- a/tests/work-items/work-items.test.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { PlaneClient } from "../../src/client/plane-client"; -import { config } from "../constants"; -import { createTestClient } from "../test-utils"; - -export async function testWorkItems() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const projectId = config.projectId; - - const workItem = await createWorkItem(client, workspaceSlug, projectId); - console.log("Created work item: ", workItem); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const retrievedWorkItem = await retrieveWorkItem( - client, - workspaceSlug, - projectId, - workItem.id - ); - console.log("Retrieved work item: ", retrievedWorkItem); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const updatedWorkItem = await updateWorkItem( - client, - workspaceSlug, - projectId, - workItem.id - ); - console.log("Updated work item: ", updatedWorkItem); - - await new Promise((resolve) => setTimeout(resolve, 1000)); - const workItems = await listWorkItems(client, workspaceSlug, projectId); - console.log("Listed work items: ", workItems); - - await new Promise((resolve) => setTimeout(resolve, 1000)); - const workItemByIdentifier = await retrieveWorkItemByIdentifier( - client, - workspaceSlug, - projectId, - workItem.sequence_id! - ); - console.log("Retrieved work item by identifier: ", workItemByIdentifier); - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const searchedWorkItems = await searchWorkItems( - client, - workspaceSlug, - projectId, - workItemByIdentifier.name - ); - console.log("Searched work items: ", searchedWorkItems); - - await new Promise((resolve) => setTimeout(resolve, 1000)); - - await deleteWorkItem(client, workspaceSlug, projectId, workItem.id); - console.log("Work item deleted: ", workItem.id); -} - -async function createWorkItem( - client: PlaneClient, - workspaceSlug: string, - projectId: string -) { - const workItem = await client.workItems.create(workspaceSlug, projectId, { - name: "Test Work Item", - description_html: "

A work item created via the Plane SDK

", - }); - return workItem; -} - -async function retrieveWorkItem( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string -) { - const workItem = await client.workItems.retrieve( - workspaceSlug, - projectId, - workItemId - ); - return workItem; -} - -async function updateWorkItem( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string -) { - const states = await client.states.list(workspaceSlug, projectId); - const labels = await client.labels.list(workspaceSlug, projectId); - - const label = labels.results[0]; - const state = states.results[0]; - - const updatedWorkItem = await client.workItems.update( - workspaceSlug, - projectId, - workItemId, - { - name: "Updated Test Work Item", - description_html: "

Updated Test Work Item Description

", - state: state ? state.id : undefined, - assignees: [config.userId], - labels: label ? [label.id] : undefined, - } - ); - return updatedWorkItem; -} - -async function listWorkItems( - client: PlaneClient, - workspaceSlug: string, - projectId: string -) { - const workItems = await client.workItems.list(workspaceSlug, projectId); - return workItems; -} - -async function deleteWorkItem( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string -) { - await client.workItems.delete(workspaceSlug, projectId, workItemId); -} - -async function retrieveWorkItemByIdentifier( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - identifier: number -) { - const project = await client.projects.retrieve(workspaceSlug, projectId); - const workItem = await client.workItems.retrieveByIdentifier( - workspaceSlug, - `${project.identifier}-${identifier}`, - ["project"] - ); - return workItem; -} - -async function searchWorkItems( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - query: string -) { - const workItems = await client.workItems.search( - workspaceSlug, - projectId, - query - ); - return workItems; -} - -if (require.main === module) { - testWorkItems().catch(console.error); -} diff --git a/tests/work-items/work-logs.test.ts b/tests/work-items/work-logs.test.ts deleted file mode 100644 index b5860f6..0000000 --- a/tests/work-items/work-logs.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { PlaneClient } from "../../src/client/plane-client"; -import { config } from "../constants"; -import { createTestClient } from "../test-utils"; - -export async function testWorkLogs() { - const client = createTestClient(); - - const workspaceSlug = config.workspaceSlug; - const projectId = config.projectId; - const workItemId = config.workItemId; - - await client.projects.update(workspaceSlug, projectId, { - is_time_tracking_enabled: true, - }); - - const project = await client.projects.retrieve(workspaceSlug, projectId); - - console.log("Time tracking enabled: ", project.is_time_tracking_enabled); - - const workLog = await createWorkLog( - client, - workspaceSlug, - projectId, - workItemId - ); - console.log("Created work log: ", workLog); - - const workLogs = await listWorkLogs( - client, - workspaceSlug, - projectId, - workItemId - ); - console.log("Listed work logs: ", workLogs); - - const updatedWorkLog = await updateWorkLog( - client, - workspaceSlug, - projectId, - workItemId, - workLog.id - ); - console.log("Updated work log: ", updatedWorkLog); - - await deleteWorkLog(client, workspaceSlug, projectId, workItemId, workLog.id); - console.log("Deleted work log: ", workLog.id); -} - -async function createWorkLog( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string -) { - return client.workItems.workLogs.create( - workspaceSlug, - projectId, - workItemId, - { - duration: 30, - description: "Test work log", - } - ); -} - -async function listWorkLogs( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string -) { - return client.workItems.workLogs.list(workspaceSlug, projectId, workItemId); -} - -async function updateWorkLog( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string, - workLogId: string -) { - return client.workItems.workLogs.update( - workspaceSlug, - projectId, - workItemId, - workLogId, - { - description: "Updated test work log", - } - ); -} - -async function deleteWorkLog( - client: PlaneClient, - workspaceSlug: string, - projectId: string, - workItemId: string, - workLogId: string -) { - return client.workItems.workLogs.delete( - workspaceSlug, - projectId, - workItemId, - workLogId - ); -} - -if (require.main === module) { - testWorkLogs().catch(console.error); -} diff --git a/tsconfig.jest.json b/tsconfig.jest.json new file mode 100644 index 0000000..686ed25 --- /dev/null +++ b/tsconfig.jest.json @@ -0,0 +1,29 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "types": ["jest", "node"] + }, + "include": [ + "src/**/*", + "tests/e2e/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file