@masonator/coolify-mcp 2.10.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -22
- package/dist/__tests__/coolify-client.test.js +531 -0
- package/dist/__tests__/mcp-server.test.js +55 -0
- package/dist/lib/coolify-client.d.ts +42 -1
- package/dist/lib/coolify-client.js +225 -0
- package/dist/lib/mcp-server.js +365 -20
- package/dist/types/coolify.d.ts +144 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,33 +9,36 @@
|
|
|
9
9
|
[](https://codecov.io/gh/StuMason/coolify-mcp)
|
|
10
10
|
[](https://mseep.ai/app/stumason-coolify-mcp)
|
|
11
11
|
|
|
12
|
-
> **The most comprehensive MCP server for Coolify** -
|
|
12
|
+
> **The most comprehensive MCP server for Coolify** - 42 optimized tools, smart diagnostics, documentation search, and batch operations for managing your self-hosted PaaS through AI assistants.
|
|
13
13
|
|
|
14
14
|
A Model Context Protocol (MCP) server for [Coolify](https://coolify.io/), enabling AI assistants to manage and debug your Coolify instances through natural language.
|
|
15
15
|
|
|
16
16
|
## Features
|
|
17
17
|
|
|
18
|
-
This MCP server provides **
|
|
19
|
-
|
|
20
|
-
| Category | Tools
|
|
21
|
-
| -------------------- |
|
|
22
|
-
| **Infrastructure** | `get_infrastructure_overview`, `get_mcp_version`, `get_version`
|
|
23
|
-
| **Diagnostics** | `diagnose_app`, `diagnose_server`, `find_issues`
|
|
24
|
-
| **Batch Operations** | `restart_project_apps`, `bulk_env_update`, `stop_all_apps`, `redeploy_project`
|
|
25
|
-
| **Servers** | `list_servers`, `get_server`, `validate_server`, `server_resources`, `server_domains`
|
|
26
|
-
| **Projects** | `projects` (list, get, create, update, delete via action param)
|
|
27
|
-
| **Environments** | `environments` (list, get, create, delete via action param)
|
|
28
|
-
| **Applications** | `list_applications`, `get_application`, `application` (CRUD), `application_logs`
|
|
29
|
-
| **Databases** | `list_databases`, `get_database`, `database` (create 8 types, delete), `database_backups` (CRUD schedules,
|
|
30
|
-
| **Services** | `list_services`, `get_service`, `service` (create, update, delete)
|
|
31
|
-
| **Control** | `control` (start/stop/restart for apps, databases, services)
|
|
32
|
-
| **Env Vars** | `env_vars` (CRUD for application and
|
|
33
|
-
| **
|
|
34
|
-
| **
|
|
35
|
-
| **
|
|
36
|
-
| **
|
|
37
|
-
| **
|
|
38
|
-
| **
|
|
18
|
+
This MCP server provides **42 token-optimized tools** for **debugging, management, and deployment**:
|
|
19
|
+
|
|
20
|
+
| Category | Tools |
|
|
21
|
+
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
22
|
+
| **Infrastructure** | `get_infrastructure_overview`, `get_mcp_version`, `get_version`, `system` (health, list_resources, enable/disable API) |
|
|
23
|
+
| **Diagnostics** | `diagnose_app`, `diagnose_server`, `find_issues` |
|
|
24
|
+
| **Batch Operations** | `restart_project_apps`, `bulk_env_update`, `stop_all_apps`, `redeploy_project` |
|
|
25
|
+
| **Servers** | `list_servers`, `get_server`, `validate_server`, `server_resources`, `server_domains` |
|
|
26
|
+
| **Projects** | `projects` (list, get, create, update, delete via action param) |
|
|
27
|
+
| **Environments** | `environments` (list, get, create, delete via action param) |
|
|
28
|
+
| **Applications** | `list_applications`, `get_application`, `application` (CRUD + delete_preview), `application_logs` |
|
|
29
|
+
| **Databases** | `list_databases`, `get_database`, `database` (create 8 types, delete), `database_backups` (CRUD schedules, executions incl. delete) |
|
|
30
|
+
| **Services** | `list_services`, `get_service`, `service` (create, update, delete) |
|
|
31
|
+
| **Control** | `control` (start/stop/restart for apps, databases, services) |
|
|
32
|
+
| **Env Vars** | `env_vars` (CRUD + bulk_update for application, service, and database env vars) |
|
|
33
|
+
| **Storages** | `storages` (list, create, update, delete persistent/file storages for apps, databases, services) |
|
|
34
|
+
| **Scheduled Tasks** | `scheduled_tasks` (list, create, update, delete, list_executions for apps and services) |
|
|
35
|
+
| **Deployments** | `list_deployments`, `deploy`, `deployment` (get, cancel, list_for_app) |
|
|
36
|
+
| **Private Keys** | `private_keys` (list, get, create, update, delete via action param) |
|
|
37
|
+
| **GitHub Apps** | `github_apps` (list, get, create, update, delete, list_repos, list_branches) |
|
|
38
|
+
| **Teams** | `teams` (list, get, get_members, get_current, get_current_members) |
|
|
39
|
+
| **Cloud Tokens** | `cloud_tokens` (Hetzner/DigitalOcean: list, get, create, update, delete, validate) |
|
|
40
|
+
| **Hetzner Cloud** | `hetzner` (list_locations, list_server_types, list_images, list_ssh_keys, create_server) |
|
|
41
|
+
| **Documentation** | `search_docs` (full-text search across Coolify docs) |
|
|
39
42
|
|
|
40
43
|
### Token-Optimized Design
|
|
41
44
|
|
|
@@ -3039,4 +3039,535 @@ describe('CoolifyClient', () => {
|
|
|
3039
3039
|
});
|
|
3040
3040
|
});
|
|
3041
3041
|
});
|
|
3042
|
+
// ===========================================================================
|
|
3043
|
+
// Application Storage endpoints
|
|
3044
|
+
// ===========================================================================
|
|
3045
|
+
describe('listApplicationStorages', () => {
|
|
3046
|
+
it('should list application storages', async () => {
|
|
3047
|
+
const mockData = { persistent_storages: [], file_storages: [] };
|
|
3048
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockData));
|
|
3049
|
+
const result = await client.listApplicationStorages('app-uuid');
|
|
3050
|
+
expect(result).toEqual(mockData);
|
|
3051
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/applications/app-uuid/storages', expect.any(Object));
|
|
3052
|
+
});
|
|
3053
|
+
});
|
|
3054
|
+
describe('createApplicationStorage', () => {
|
|
3055
|
+
it('should create application storage', async () => {
|
|
3056
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Storage created.' }));
|
|
3057
|
+
const result = await client.createApplicationStorage('app-uuid', {
|
|
3058
|
+
type: 'persistent',
|
|
3059
|
+
mount_path: '/data',
|
|
3060
|
+
name: 'my-vol',
|
|
3061
|
+
});
|
|
3062
|
+
expect(result).toEqual({ message: 'Storage created.' });
|
|
3063
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/applications/app-uuid/storages', expect.objectContaining({ method: 'POST' }));
|
|
3064
|
+
});
|
|
3065
|
+
});
|
|
3066
|
+
describe('updateApplicationStorage', () => {
|
|
3067
|
+
it('should update application storage', async () => {
|
|
3068
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Storage updated.' }));
|
|
3069
|
+
const result = await client.updateApplicationStorage('app-uuid', {
|
|
3070
|
+
uuid: 'stor-uuid',
|
|
3071
|
+
type: 'persistent',
|
|
3072
|
+
name: 'renamed',
|
|
3073
|
+
});
|
|
3074
|
+
expect(result).toEqual({ message: 'Storage updated.' });
|
|
3075
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/applications/app-uuid/storages', expect.objectContaining({ method: 'PATCH' }));
|
|
3076
|
+
});
|
|
3077
|
+
});
|
|
3078
|
+
describe('deleteApplicationStorage', () => {
|
|
3079
|
+
it('should delete application storage', async () => {
|
|
3080
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Storage deleted.' }));
|
|
3081
|
+
const result = await client.deleteApplicationStorage('app-uuid', 'stor-uuid');
|
|
3082
|
+
expect(result).toEqual({ message: 'Storage deleted.' });
|
|
3083
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/applications/app-uuid/storages/stor-uuid', expect.objectContaining({ method: 'DELETE' }));
|
|
3084
|
+
});
|
|
3085
|
+
});
|
|
3086
|
+
// ===========================================================================
|
|
3087
|
+
// Application Scheduled Task endpoints
|
|
3088
|
+
// ===========================================================================
|
|
3089
|
+
describe('listApplicationScheduledTasks', () => {
|
|
3090
|
+
it('should list scheduled tasks', async () => {
|
|
3091
|
+
const mockTasks = [
|
|
3092
|
+
{
|
|
3093
|
+
id: 1,
|
|
3094
|
+
uuid: 'task-1',
|
|
3095
|
+
name: 'backup',
|
|
3096
|
+
command: 'pg_dump',
|
|
3097
|
+
frequency: '0 * * * *',
|
|
3098
|
+
enabled: true,
|
|
3099
|
+
timeout: 300,
|
|
3100
|
+
created_at: '2024-01-01',
|
|
3101
|
+
updated_at: '2024-01-01',
|
|
3102
|
+
},
|
|
3103
|
+
];
|
|
3104
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockTasks));
|
|
3105
|
+
const result = await client.listApplicationScheduledTasks('app-uuid');
|
|
3106
|
+
expect(result).toEqual(mockTasks);
|
|
3107
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/applications/app-uuid/scheduled-tasks', expect.any(Object));
|
|
3108
|
+
});
|
|
3109
|
+
});
|
|
3110
|
+
describe('createApplicationScheduledTask', () => {
|
|
3111
|
+
it('should create a scheduled task', async () => {
|
|
3112
|
+
const mockTask = {
|
|
3113
|
+
id: 1,
|
|
3114
|
+
uuid: 'task-1',
|
|
3115
|
+
name: 'backup',
|
|
3116
|
+
command: 'pg_dump',
|
|
3117
|
+
frequency: '0 * * * *',
|
|
3118
|
+
enabled: true,
|
|
3119
|
+
timeout: 300,
|
|
3120
|
+
created_at: '2024-01-01',
|
|
3121
|
+
updated_at: '2024-01-01',
|
|
3122
|
+
};
|
|
3123
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockTask));
|
|
3124
|
+
const result = await client.createApplicationScheduledTask('app-uuid', {
|
|
3125
|
+
name: 'backup',
|
|
3126
|
+
command: 'pg_dump',
|
|
3127
|
+
frequency: '0 * * * *',
|
|
3128
|
+
});
|
|
3129
|
+
expect(result).toEqual(mockTask);
|
|
3130
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/applications/app-uuid/scheduled-tasks', expect.objectContaining({ method: 'POST' }));
|
|
3131
|
+
});
|
|
3132
|
+
});
|
|
3133
|
+
describe('updateApplicationScheduledTask', () => {
|
|
3134
|
+
it('should update a scheduled task', async () => {
|
|
3135
|
+
const mockTask = {
|
|
3136
|
+
id: 1,
|
|
3137
|
+
uuid: 'task-1',
|
|
3138
|
+
name: 'backup-v2',
|
|
3139
|
+
command: 'pg_dump -Fc',
|
|
3140
|
+
frequency: '0 * * * *',
|
|
3141
|
+
enabled: true,
|
|
3142
|
+
timeout: 300,
|
|
3143
|
+
created_at: '2024-01-01',
|
|
3144
|
+
updated_at: '2024-01-01',
|
|
3145
|
+
};
|
|
3146
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockTask));
|
|
3147
|
+
const result = await client.updateApplicationScheduledTask('app-uuid', 'task-1', {
|
|
3148
|
+
name: 'backup-v2',
|
|
3149
|
+
});
|
|
3150
|
+
expect(result).toEqual(mockTask);
|
|
3151
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/applications/app-uuid/scheduled-tasks/task-1', expect.objectContaining({ method: 'PATCH' }));
|
|
3152
|
+
});
|
|
3153
|
+
});
|
|
3154
|
+
describe('deleteApplicationScheduledTask', () => {
|
|
3155
|
+
it('should delete a scheduled task', async () => {
|
|
3156
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Scheduled task deleted.' }));
|
|
3157
|
+
const result = await client.deleteApplicationScheduledTask('app-uuid', 'task-1');
|
|
3158
|
+
expect(result).toEqual({ message: 'Scheduled task deleted.' });
|
|
3159
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/applications/app-uuid/scheduled-tasks/task-1', expect.objectContaining({ method: 'DELETE' }));
|
|
3160
|
+
});
|
|
3161
|
+
});
|
|
3162
|
+
describe('listApplicationScheduledTaskExecutions', () => {
|
|
3163
|
+
it('should list task executions', async () => {
|
|
3164
|
+
const mockExecs = [
|
|
3165
|
+
{
|
|
3166
|
+
uuid: 'exec-1',
|
|
3167
|
+
status: 'success',
|
|
3168
|
+
retry_count: 0,
|
|
3169
|
+
created_at: '2024-01-01',
|
|
3170
|
+
updated_at: '2024-01-01',
|
|
3171
|
+
},
|
|
3172
|
+
];
|
|
3173
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockExecs));
|
|
3174
|
+
const result = await client.listApplicationScheduledTaskExecutions('app-uuid', 'task-1');
|
|
3175
|
+
expect(result).toEqual(mockExecs);
|
|
3176
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/applications/app-uuid/scheduled-tasks/task-1/executions', expect.any(Object));
|
|
3177
|
+
});
|
|
3178
|
+
});
|
|
3179
|
+
// ===========================================================================
|
|
3180
|
+
// Application Preview endpoint
|
|
3181
|
+
// ===========================================================================
|
|
3182
|
+
describe('deleteApplicationPreview', () => {
|
|
3183
|
+
it('should delete a preview deployment', async () => {
|
|
3184
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Preview deleted.' }));
|
|
3185
|
+
const result = await client.deleteApplicationPreview('app-uuid', 42);
|
|
3186
|
+
expect(result).toEqual({ message: 'Preview deleted.' });
|
|
3187
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/applications/app-uuid/previews/42', expect.objectContaining({ method: 'DELETE' }));
|
|
3188
|
+
});
|
|
3189
|
+
});
|
|
3190
|
+
// ===========================================================================
|
|
3191
|
+
// Database Environment Variable endpoints
|
|
3192
|
+
// ===========================================================================
|
|
3193
|
+
describe('listDatabaseEnvVars', () => {
|
|
3194
|
+
it('should list database env vars', async () => {
|
|
3195
|
+
const mockEnvs = [
|
|
3196
|
+
{
|
|
3197
|
+
id: 1,
|
|
3198
|
+
uuid: 'env-1',
|
|
3199
|
+
key: 'DB_HOST',
|
|
3200
|
+
value: 'localhost',
|
|
3201
|
+
is_build_time: false,
|
|
3202
|
+
is_literal: false,
|
|
3203
|
+
is_multiline: false,
|
|
3204
|
+
is_preview: false,
|
|
3205
|
+
is_shared: false,
|
|
3206
|
+
is_shown_once: false,
|
|
3207
|
+
created_at: '2024-01-01',
|
|
3208
|
+
updated_at: '2024-01-01',
|
|
3209
|
+
},
|
|
3210
|
+
];
|
|
3211
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockEnvs));
|
|
3212
|
+
const result = await client.listDatabaseEnvVars('db-uuid');
|
|
3213
|
+
expect(result).toEqual(mockEnvs);
|
|
3214
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/envs', expect.any(Object));
|
|
3215
|
+
});
|
|
3216
|
+
});
|
|
3217
|
+
describe('createDatabaseEnvVar', () => {
|
|
3218
|
+
it('should create a database env var', async () => {
|
|
3219
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'env-1' }));
|
|
3220
|
+
const result = await client.createDatabaseEnvVar('db-uuid', {
|
|
3221
|
+
key: 'DB_HOST',
|
|
3222
|
+
value: 'localhost',
|
|
3223
|
+
});
|
|
3224
|
+
expect(result).toEqual({ uuid: 'env-1' });
|
|
3225
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/envs', expect.objectContaining({ method: 'POST' }));
|
|
3226
|
+
});
|
|
3227
|
+
});
|
|
3228
|
+
describe('updateDatabaseEnvVar', () => {
|
|
3229
|
+
it('should update a database env var', async () => {
|
|
3230
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Updated.' }));
|
|
3231
|
+
const result = await client.updateDatabaseEnvVar('db-uuid', {
|
|
3232
|
+
key: 'DB_HOST',
|
|
3233
|
+
value: '127.0.0.1',
|
|
3234
|
+
});
|
|
3235
|
+
expect(result).toEqual({ message: 'Updated.' });
|
|
3236
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/envs', expect.objectContaining({ method: 'PATCH' }));
|
|
3237
|
+
});
|
|
3238
|
+
});
|
|
3239
|
+
describe('bulkUpdateDatabaseEnvVars', () => {
|
|
3240
|
+
it('should bulk update database env vars', async () => {
|
|
3241
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Bulk updated.' }));
|
|
3242
|
+
const result = await client.bulkUpdateDatabaseEnvVars('db-uuid', {
|
|
3243
|
+
data: [{ key: 'A', value: '1' }],
|
|
3244
|
+
});
|
|
3245
|
+
expect(result).toEqual({ message: 'Bulk updated.' });
|
|
3246
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/envs/bulk', expect.objectContaining({ method: 'PATCH' }));
|
|
3247
|
+
});
|
|
3248
|
+
});
|
|
3249
|
+
describe('deleteDatabaseEnvVar', () => {
|
|
3250
|
+
it('should delete a database env var', async () => {
|
|
3251
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Deleted.' }));
|
|
3252
|
+
const result = await client.deleteDatabaseEnvVar('db-uuid', 'env-uuid');
|
|
3253
|
+
expect(result).toEqual({ message: 'Deleted.' });
|
|
3254
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/envs/env-uuid', expect.objectContaining({ method: 'DELETE' }));
|
|
3255
|
+
});
|
|
3256
|
+
});
|
|
3257
|
+
// ===========================================================================
|
|
3258
|
+
// Database Storage endpoints
|
|
3259
|
+
// ===========================================================================
|
|
3260
|
+
describe('listDatabaseStorages', () => {
|
|
3261
|
+
it('should list database storages', async () => {
|
|
3262
|
+
const mockData = { persistent_storages: [], file_storages: [] };
|
|
3263
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockData));
|
|
3264
|
+
const result = await client.listDatabaseStorages('db-uuid');
|
|
3265
|
+
expect(result).toEqual(mockData);
|
|
3266
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/storages', expect.any(Object));
|
|
3267
|
+
});
|
|
3268
|
+
});
|
|
3269
|
+
describe('createDatabaseStorage', () => {
|
|
3270
|
+
it('should create database storage', async () => {
|
|
3271
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Created.' }));
|
|
3272
|
+
const result = await client.createDatabaseStorage('db-uuid', {
|
|
3273
|
+
type: 'persistent',
|
|
3274
|
+
mount_path: '/data',
|
|
3275
|
+
});
|
|
3276
|
+
expect(result).toEqual({ message: 'Created.' });
|
|
3277
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/storages', expect.objectContaining({ method: 'POST' }));
|
|
3278
|
+
});
|
|
3279
|
+
});
|
|
3280
|
+
describe('updateDatabaseStorage', () => {
|
|
3281
|
+
it('should update database storage', async () => {
|
|
3282
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Updated.' }));
|
|
3283
|
+
const result = await client.updateDatabaseStorage('db-uuid', {
|
|
3284
|
+
type: 'persistent',
|
|
3285
|
+
name: 'new-name',
|
|
3286
|
+
});
|
|
3287
|
+
expect(result).toEqual({ message: 'Updated.' });
|
|
3288
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/storages', expect.objectContaining({ method: 'PATCH' }));
|
|
3289
|
+
});
|
|
3290
|
+
});
|
|
3291
|
+
describe('deleteDatabaseStorage', () => {
|
|
3292
|
+
it('should delete database storage', async () => {
|
|
3293
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Deleted.' }));
|
|
3294
|
+
const result = await client.deleteDatabaseStorage('db-uuid', 'stor-uuid');
|
|
3295
|
+
expect(result).toEqual({ message: 'Deleted.' });
|
|
3296
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/storages/stor-uuid', expect.objectContaining({ method: 'DELETE' }));
|
|
3297
|
+
});
|
|
3298
|
+
});
|
|
3299
|
+
// ===========================================================================
|
|
3300
|
+
// Delete Backup Execution endpoint
|
|
3301
|
+
// ===========================================================================
|
|
3302
|
+
describe('deleteBackupExecution', () => {
|
|
3303
|
+
it('should delete a backup execution', async () => {
|
|
3304
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Deleted.' }));
|
|
3305
|
+
const result = await client.deleteBackupExecution('db-uuid', 'backup-uuid', 'exec-uuid');
|
|
3306
|
+
expect(result).toEqual({ message: 'Deleted.' });
|
|
3307
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/backups/backup-uuid/executions/exec-uuid', expect.objectContaining({ method: 'DELETE' }));
|
|
3308
|
+
});
|
|
3309
|
+
});
|
|
3310
|
+
// ===========================================================================
|
|
3311
|
+
// Service Environment Variable bulk endpoint
|
|
3312
|
+
// ===========================================================================
|
|
3313
|
+
describe('bulkUpdateServiceEnvVars', () => {
|
|
3314
|
+
it('should bulk update service env vars', async () => {
|
|
3315
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Bulk updated.' }));
|
|
3316
|
+
const result = await client.bulkUpdateServiceEnvVars('svc-uuid', {
|
|
3317
|
+
data: [{ key: 'A', value: '1' }],
|
|
3318
|
+
});
|
|
3319
|
+
expect(result).toEqual({ message: 'Bulk updated.' });
|
|
3320
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/svc-uuid/envs/bulk', expect.objectContaining({ method: 'PATCH' }));
|
|
3321
|
+
});
|
|
3322
|
+
});
|
|
3323
|
+
// ===========================================================================
|
|
3324
|
+
// Service Storage endpoints
|
|
3325
|
+
// ===========================================================================
|
|
3326
|
+
describe('listServiceStorages', () => {
|
|
3327
|
+
it('should list service storages', async () => {
|
|
3328
|
+
const mockData = { persistent_storages: [], file_storages: [] };
|
|
3329
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockData));
|
|
3330
|
+
const result = await client.listServiceStorages('svc-uuid');
|
|
3331
|
+
expect(result).toEqual(mockData);
|
|
3332
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/svc-uuid/storages', expect.any(Object));
|
|
3333
|
+
});
|
|
3334
|
+
});
|
|
3335
|
+
describe('createServiceStorage', () => {
|
|
3336
|
+
it('should create service storage', async () => {
|
|
3337
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Created.' }));
|
|
3338
|
+
const result = await client.createServiceStorage('svc-uuid', {
|
|
3339
|
+
type: 'file',
|
|
3340
|
+
mount_path: '/config',
|
|
3341
|
+
});
|
|
3342
|
+
expect(result).toEqual({ message: 'Created.' });
|
|
3343
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/svc-uuid/storages', expect.objectContaining({ method: 'POST' }));
|
|
3344
|
+
});
|
|
3345
|
+
});
|
|
3346
|
+
describe('updateServiceStorage', () => {
|
|
3347
|
+
it('should update service storage', async () => {
|
|
3348
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Updated.' }));
|
|
3349
|
+
const result = await client.updateServiceStorage('svc-uuid', {
|
|
3350
|
+
type: 'file',
|
|
3351
|
+
content: 'new content',
|
|
3352
|
+
});
|
|
3353
|
+
expect(result).toEqual({ message: 'Updated.' });
|
|
3354
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/svc-uuid/storages', expect.objectContaining({ method: 'PATCH' }));
|
|
3355
|
+
});
|
|
3356
|
+
});
|
|
3357
|
+
describe('deleteServiceStorage', () => {
|
|
3358
|
+
it('should delete service storage', async () => {
|
|
3359
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Deleted.' }));
|
|
3360
|
+
const result = await client.deleteServiceStorage('svc-uuid', 'stor-uuid');
|
|
3361
|
+
expect(result).toEqual({ message: 'Deleted.' });
|
|
3362
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/svc-uuid/storages/stor-uuid', expect.objectContaining({ method: 'DELETE' }));
|
|
3363
|
+
});
|
|
3364
|
+
});
|
|
3365
|
+
// ===========================================================================
|
|
3366
|
+
// Service Scheduled Task endpoints
|
|
3367
|
+
// ===========================================================================
|
|
3368
|
+
describe('listServiceScheduledTasks', () => {
|
|
3369
|
+
it('should list service scheduled tasks', async () => {
|
|
3370
|
+
mockFetch.mockResolvedValueOnce(mockResponse([]));
|
|
3371
|
+
const result = await client.listServiceScheduledTasks('svc-uuid');
|
|
3372
|
+
expect(result).toEqual([]);
|
|
3373
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/svc-uuid/scheduled-tasks', expect.any(Object));
|
|
3374
|
+
});
|
|
3375
|
+
});
|
|
3376
|
+
describe('createServiceScheduledTask', () => {
|
|
3377
|
+
it('should create a service scheduled task', async () => {
|
|
3378
|
+
const mockTask = {
|
|
3379
|
+
id: 1,
|
|
3380
|
+
uuid: 'task-1',
|
|
3381
|
+
name: 'cleanup',
|
|
3382
|
+
command: 'rm -rf /tmp/*',
|
|
3383
|
+
frequency: '0 0 * * *',
|
|
3384
|
+
enabled: true,
|
|
3385
|
+
timeout: 300,
|
|
3386
|
+
created_at: '2024-01-01',
|
|
3387
|
+
updated_at: '2024-01-01',
|
|
3388
|
+
};
|
|
3389
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockTask));
|
|
3390
|
+
const result = await client.createServiceScheduledTask('svc-uuid', {
|
|
3391
|
+
name: 'cleanup',
|
|
3392
|
+
command: 'rm -rf /tmp/*',
|
|
3393
|
+
frequency: '0 0 * * *',
|
|
3394
|
+
});
|
|
3395
|
+
expect(result).toEqual(mockTask);
|
|
3396
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/svc-uuid/scheduled-tasks', expect.objectContaining({ method: 'POST' }));
|
|
3397
|
+
});
|
|
3398
|
+
});
|
|
3399
|
+
describe('updateServiceScheduledTask', () => {
|
|
3400
|
+
it('should update a service scheduled task', async () => {
|
|
3401
|
+
const mockTask = {
|
|
3402
|
+
id: 1,
|
|
3403
|
+
uuid: 'task-1',
|
|
3404
|
+
name: 'cleanup-v2',
|
|
3405
|
+
command: 'rm -rf /tmp/*',
|
|
3406
|
+
frequency: '0 0 * * *',
|
|
3407
|
+
enabled: true,
|
|
3408
|
+
timeout: 600,
|
|
3409
|
+
created_at: '2024-01-01',
|
|
3410
|
+
updated_at: '2024-01-01',
|
|
3411
|
+
};
|
|
3412
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockTask));
|
|
3413
|
+
const result = await client.updateServiceScheduledTask('svc-uuid', 'task-1', {
|
|
3414
|
+
timeout: 600,
|
|
3415
|
+
});
|
|
3416
|
+
expect(result).toEqual(mockTask);
|
|
3417
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/svc-uuid/scheduled-tasks/task-1', expect.objectContaining({ method: 'PATCH' }));
|
|
3418
|
+
});
|
|
3419
|
+
});
|
|
3420
|
+
describe('deleteServiceScheduledTask', () => {
|
|
3421
|
+
it('should delete a service scheduled task', async () => {
|
|
3422
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Deleted.' }));
|
|
3423
|
+
const result = await client.deleteServiceScheduledTask('svc-uuid', 'task-1');
|
|
3424
|
+
expect(result).toEqual({ message: 'Deleted.' });
|
|
3425
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/svc-uuid/scheduled-tasks/task-1', expect.objectContaining({ method: 'DELETE' }));
|
|
3426
|
+
});
|
|
3427
|
+
});
|
|
3428
|
+
describe('listServiceScheduledTaskExecutions', () => {
|
|
3429
|
+
it('should list service task executions', async () => {
|
|
3430
|
+
mockFetch.mockResolvedValueOnce(mockResponse([]));
|
|
3431
|
+
const result = await client.listServiceScheduledTaskExecutions('svc-uuid', 'task-1');
|
|
3432
|
+
expect(result).toEqual([]);
|
|
3433
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/svc-uuid/scheduled-tasks/task-1/executions', expect.any(Object));
|
|
3434
|
+
});
|
|
3435
|
+
});
|
|
3436
|
+
// ===========================================================================
|
|
3437
|
+
// Hetzner Cloud endpoints
|
|
3438
|
+
// ===========================================================================
|
|
3439
|
+
describe('listHetznerLocations', () => {
|
|
3440
|
+
it('should list Hetzner locations', async () => {
|
|
3441
|
+
const mockLocs = [
|
|
3442
|
+
{
|
|
3443
|
+
id: 1,
|
|
3444
|
+
name: 'nbg1',
|
|
3445
|
+
description: 'Nuremberg',
|
|
3446
|
+
country: 'DE',
|
|
3447
|
+
city: 'Nuremberg',
|
|
3448
|
+
latitude: 49.45,
|
|
3449
|
+
longitude: 11.08,
|
|
3450
|
+
},
|
|
3451
|
+
];
|
|
3452
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockLocs));
|
|
3453
|
+
const result = await client.listHetznerLocations('token-uuid');
|
|
3454
|
+
expect(result).toEqual(mockLocs);
|
|
3455
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/hetzner/locations?cloud_provider_token_uuid=token-uuid', expect.any(Object));
|
|
3456
|
+
});
|
|
3457
|
+
});
|
|
3458
|
+
describe('listHetznerServerTypes', () => {
|
|
3459
|
+
it('should list Hetzner server types', async () => {
|
|
3460
|
+
mockFetch.mockResolvedValueOnce(mockResponse([]));
|
|
3461
|
+
const result = await client.listHetznerServerTypes('token-uuid');
|
|
3462
|
+
expect(result).toEqual([]);
|
|
3463
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/hetzner/server-types?cloud_provider_token_uuid=token-uuid', expect.any(Object));
|
|
3464
|
+
});
|
|
3465
|
+
});
|
|
3466
|
+
describe('listHetznerImages', () => {
|
|
3467
|
+
it('should list Hetzner images', async () => {
|
|
3468
|
+
mockFetch.mockResolvedValueOnce(mockResponse([]));
|
|
3469
|
+
const result = await client.listHetznerImages('token-uuid');
|
|
3470
|
+
expect(result).toEqual([]);
|
|
3471
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/hetzner/images?cloud_provider_token_uuid=token-uuid', expect.any(Object));
|
|
3472
|
+
});
|
|
3473
|
+
});
|
|
3474
|
+
describe('listHetznerSSHKeys', () => {
|
|
3475
|
+
it('should list Hetzner SSH keys', async () => {
|
|
3476
|
+
mockFetch.mockResolvedValueOnce(mockResponse([]));
|
|
3477
|
+
const result = await client.listHetznerSSHKeys('token-uuid');
|
|
3478
|
+
expect(result).toEqual([]);
|
|
3479
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/hetzner/ssh-keys?cloud_provider_token_uuid=token-uuid', expect.any(Object));
|
|
3480
|
+
});
|
|
3481
|
+
});
|
|
3482
|
+
describe('createHetznerServer', () => {
|
|
3483
|
+
it('should create a Hetzner server', async () => {
|
|
3484
|
+
const mockResp = { uuid: 'srv-uuid', hetzner_server_id: 12345, ip: '1.2.3.4' };
|
|
3485
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockResp));
|
|
3486
|
+
const result = await client.createHetznerServer({
|
|
3487
|
+
location: 'nbg1',
|
|
3488
|
+
server_type: 'cx11',
|
|
3489
|
+
image: 15512617,
|
|
3490
|
+
private_key_uuid: 'key-uuid',
|
|
3491
|
+
});
|
|
3492
|
+
expect(result).toEqual(mockResp);
|
|
3493
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/servers/hetzner', expect.objectContaining({ method: 'POST' }));
|
|
3494
|
+
});
|
|
3495
|
+
});
|
|
3496
|
+
// ===========================================================================
|
|
3497
|
+
// GitHub App Repository endpoints
|
|
3498
|
+
// ===========================================================================
|
|
3499
|
+
describe('listGitHubAppRepositories', () => {
|
|
3500
|
+
it('should list GitHub App repositories', async () => {
|
|
3501
|
+
const mockRepos = [
|
|
3502
|
+
{
|
|
3503
|
+
id: 1,
|
|
3504
|
+
name: 'my-repo',
|
|
3505
|
+
full_name: 'org/my-repo',
|
|
3506
|
+
private: true,
|
|
3507
|
+
html_url: 'https://github.com/org/my-repo',
|
|
3508
|
+
default_branch: 'main',
|
|
3509
|
+
},
|
|
3510
|
+
];
|
|
3511
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ repositories: mockRepos }));
|
|
3512
|
+
const result = await client.listGitHubAppRepositories(123);
|
|
3513
|
+
expect(result).toEqual(mockRepos);
|
|
3514
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/github-apps/123/repositories', expect.any(Object));
|
|
3515
|
+
});
|
|
3516
|
+
it('should return empty array when no repositories', async () => {
|
|
3517
|
+
mockFetch.mockResolvedValueOnce(mockResponse({}));
|
|
3518
|
+
const result = await client.listGitHubAppRepositories(123);
|
|
3519
|
+
expect(result).toEqual([]);
|
|
3520
|
+
});
|
|
3521
|
+
});
|
|
3522
|
+
describe('listGitHubAppBranches', () => {
|
|
3523
|
+
it('should list branches for a repo', async () => {
|
|
3524
|
+
const mockBranches = [{ name: 'main' }, { name: 'develop' }];
|
|
3525
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockBranches));
|
|
3526
|
+
const result = await client.listGitHubAppBranches(123, 'org', 'my-repo');
|
|
3527
|
+
expect(result).toEqual(mockBranches);
|
|
3528
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/github-apps/123/repositories/org/my-repo/branches', expect.any(Object));
|
|
3529
|
+
});
|
|
3530
|
+
});
|
|
3531
|
+
// ===========================================================================
|
|
3532
|
+
// Resources endpoint
|
|
3533
|
+
// ===========================================================================
|
|
3534
|
+
describe('listResources', () => {
|
|
3535
|
+
it('should list all resources', async () => {
|
|
3536
|
+
const mockData = [{ uuid: 'r1', type: 'application' }];
|
|
3537
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockData));
|
|
3538
|
+
const result = await client.listResources();
|
|
3539
|
+
expect(result).toEqual(mockData);
|
|
3540
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/resources', expect.any(Object));
|
|
3541
|
+
});
|
|
3542
|
+
});
|
|
3543
|
+
// ===========================================================================
|
|
3544
|
+
// Health endpoint
|
|
3545
|
+
// ===========================================================================
|
|
3546
|
+
describe('getHealth', () => {
|
|
3547
|
+
it('should check API health', async () => {
|
|
3548
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'OK' }));
|
|
3549
|
+
const result = await client.getHealth();
|
|
3550
|
+
expect(result).toEqual({ message: 'OK' });
|
|
3551
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/health', expect.any(Object));
|
|
3552
|
+
});
|
|
3553
|
+
});
|
|
3554
|
+
// ===========================================================================
|
|
3555
|
+
// API Enable/Disable endpoints
|
|
3556
|
+
// ===========================================================================
|
|
3557
|
+
describe('enableApi', () => {
|
|
3558
|
+
it('should enable the API', async () => {
|
|
3559
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'API enabled.' }));
|
|
3560
|
+
const result = await client.enableApi();
|
|
3561
|
+
expect(result).toEqual({ message: 'API enabled.' });
|
|
3562
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/enable', expect.objectContaining({ method: 'GET' }));
|
|
3563
|
+
});
|
|
3564
|
+
});
|
|
3565
|
+
describe('disableApi', () => {
|
|
3566
|
+
it('should disable the API', async () => {
|
|
3567
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'API disabled.' }));
|
|
3568
|
+
const result = await client.disableApi();
|
|
3569
|
+
expect(result).toEqual({ message: 'API disabled.' });
|
|
3570
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/disable', expect.objectContaining({ method: 'GET' }));
|
|
3571
|
+
});
|
|
3572
|
+
});
|
|
3042
3573
|
});
|