@ever-works/urlbox-plugin 1.0.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/LICENSE +661 -0
- package/README.md +80 -0
- package/package.json +82 -0
- package/src/__tests__/urlbox.plugin.spec.ts +353 -0
- package/src/index.ts +2 -0
- package/src/urlbox.plugin.ts +406 -0
- package/tsconfig.json +23 -0
- package/tsup.config.ts +12 -0
- package/vitest.config.ts +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# @ever-works/urlbox-plugin
|
|
2
|
+
|
|
3
|
+
Urlbox screenshot plugin for Ever Works - capture website screenshots using the Urlbox Screenshot API
|
|
4
|
+
|
|
5
|
+
## Plugin metadata
|
|
6
|
+
|
|
7
|
+
| Field | Value |
|
|
8
|
+
| ------------ | --------------- |
|
|
9
|
+
| ID | `urlbox` |
|
|
10
|
+
| Category | `screenshot` |
|
|
11
|
+
| Capabilities | `screenshot` |
|
|
12
|
+
| Author | Ever Works Team |
|
|
13
|
+
| License | AGPL-3.0 |
|
|
14
|
+
| Built-in | no |
|
|
15
|
+
| Auto-enable | no |
|
|
16
|
+
|
|
17
|
+
## What does Urlbox do?
|
|
18
|
+
|
|
19
|
+
Urlbox is a screenshot capture API. When work items include source URLs, this plugin automatically generates preview images by capturing a screenshot of each page.
|
|
20
|
+
|
|
21
|
+
## Why use it?
|
|
22
|
+
|
|
23
|
+
- **Automated capture** — preview images are generated for each work item without manual effort
|
|
24
|
+
- **Consistent output** — every screenshot uses the same viewport size, format, and rendering settings
|
|
25
|
+
- **Ad and cookie banner blocking** — captures clean screenshots free of ads and cookie banners
|
|
26
|
+
- **Retina rendering** — supports HiDPI output for crisp images on high-resolution displays
|
|
27
|
+
- **Multiple formats** — supports PNG, JPG, and WebP output with configurable quality
|
|
28
|
+
|
|
29
|
+
## How it works in Ever Works
|
|
30
|
+
|
|
31
|
+
During work generation, the screenshot facade sends capture requests to Urlbox for items that have a source URL. The resulting images are used as item preview thumbnails. You can configure viewport dimensions, image format, quality, retina rendering, and ad/cookie blocking behavior.
|
|
32
|
+
|
|
33
|
+
## Getting started
|
|
34
|
+
|
|
35
|
+
1. Sign up at [urlbox.com](https://urlbox.com)
|
|
36
|
+
2. Copy your API key and API secret
|
|
37
|
+
3. Enable the Urlbox plugin on this page
|
|
38
|
+
4. Enter your credentials in the settings below
|
|
39
|
+
|
|
40
|
+
## Settings
|
|
41
|
+
|
|
42
|
+
- **API Key** (required, secret) — your Urlbox API key; falls back to `PLUGIN_URLBOX_API_KEY`.
|
|
43
|
+
- **API Secret** (secret) — used to generate signed render links; falls back to `PLUGIN_URLBOX_API_SECRET`.
|
|
44
|
+
- **Viewport Width / Height** — default capture viewport in pixels (defaults: 1280 x 1024).
|
|
45
|
+
- **Image Format** — output format: `png`, `jpg`, `jpeg`, or `webp` (default: `png`).
|
|
46
|
+
- **Image Quality** — quality for lossy formats, range 1–100 (default: 80).
|
|
47
|
+
- **Retina** — enable HiDPI rendering at 2x device scale factor (default: off).
|
|
48
|
+
- **Block Ads** — strip ads from captured pages (default: on).
|
|
49
|
+
- **Hide Cookie Banners** — remove cookie consent banners (default: on).
|
|
50
|
+
|
|
51
|
+
## Troubleshooting
|
|
52
|
+
|
|
53
|
+
| Symptom | Likely cause | Fix |
|
|
54
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
55
|
+
| `401` / `Authentication failed` | API key (or signing secret) missing or wrong | Re-enter the credential(s) from the Urlbox dashboard, or set `PLUGIN_URLBOX_API_KEY` (and signing-secret env var if applicable) for default fallback |
|
|
56
|
+
| Black / blank / `null` `imageUrl` returned | Target page failed to render within the configured timeout, or is blocked by anti-bot protection | Increase the timeout, enable wait-for-network-idle / `full_page` mode, or set a custom `user_agent` and `viewport` |
|
|
57
|
+
| Plugin not used for screenshot capture | Another screenshot plugin is set as the default | In **Settings → Plugins**, set `urlbox` as the default for `screenshot`, or disable competing plugins |
|
|
58
|
+
| Quota exhausted / `429` | Monthly / per-minute screenshot cap reached on Urlbox | Throttle calls, wait for the quota reset, or upgrade the plan in the Urlbox dashboard |
|
|
59
|
+
| `healthCheck` reports unhealthy | Credential invalid OR Urlbox endpoint unreachable from the host | Verify the credential with a manual call to the upstream API and confirm outbound HTTPS is allowed by the firewall |
|
|
60
|
+
|
|
61
|
+
## Local development
|
|
62
|
+
|
|
63
|
+
This plugin ships built-in with the Ever Works platform. To work on it locally from the monorepo root:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pnpm install
|
|
67
|
+
pnpm --filter @ever-works/urlbox-plugin build
|
|
68
|
+
pnpm --filter @ever-works/urlbox-plugin test
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Documentation
|
|
72
|
+
|
|
73
|
+
- [Ever Works documentation](https://docs.ever.works)
|
|
74
|
+
- [Ever Works repository](https://github.com/ever-works/ever-works)
|
|
75
|
+
- [Plugin system](../../plugin/README.md)
|
|
76
|
+
- [Urlbox homepage](https://urlbox.com)
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
AGPL-3.0
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ever-works/urlbox-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Urlbox screenshot plugin for Ever Works - capture website screenshots using the Urlbox Screenshot API",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Ever Co. LTD",
|
|
7
|
+
"email": "ever@ever.co",
|
|
8
|
+
"url": "https://ever.co"
|
|
9
|
+
},
|
|
10
|
+
"license": "AGPL-3.0",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.cjs",
|
|
13
|
+
"module": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"require": "./dist/index.cjs"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"urlbox": "^2.2.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@ever-works/plugin": "1.1.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.0.0",
|
|
30
|
+
"tsup": "^8.4.0",
|
|
31
|
+
"typescript": "^5.7.3",
|
|
32
|
+
"vitest": "^4.1.0",
|
|
33
|
+
"@ever-works/plugin": "1.1.0"
|
|
34
|
+
},
|
|
35
|
+
"everworks": {
|
|
36
|
+
"plugin": {
|
|
37
|
+
"id": "urlbox",
|
|
38
|
+
"name": "Urlbox",
|
|
39
|
+
"version": "1.0.0",
|
|
40
|
+
"category": "screenshot",
|
|
41
|
+
"capabilities": [
|
|
42
|
+
"screenshot"
|
|
43
|
+
],
|
|
44
|
+
"description": "Capture website screenshots using the Urlbox Screenshot API. Supports configurable viewport sizes, formats, retina rendering, and advanced options like ad blocking and cookie banner removal.",
|
|
45
|
+
"author": {
|
|
46
|
+
"name": "Ever Works Team"
|
|
47
|
+
},
|
|
48
|
+
"license": "AGPL-3.0",
|
|
49
|
+
"systemPlugin": false,
|
|
50
|
+
"builtIn": false,
|
|
51
|
+
"envVars": [
|
|
52
|
+
{
|
|
53
|
+
"name": "PLUGIN_URLBOX_API_KEY",
|
|
54
|
+
"required": false,
|
|
55
|
+
"secret": true,
|
|
56
|
+
"description": "Urlbox API key (optional - can be set via admin/user settings)"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "PLUGIN_URLBOX_API_SECRET",
|
|
60
|
+
"required": false,
|
|
61
|
+
"secret": true,
|
|
62
|
+
"description": "Urlbox API secret for signed render links (optional - can be set via admin/user settings)"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"distribution": "registry"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"private": false,
|
|
69
|
+
"publishConfig": {
|
|
70
|
+
"access": "restricted",
|
|
71
|
+
"registry": "https://registry.npmjs.org"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"build": "tsc --noEmit && tsup",
|
|
75
|
+
"dev": "tsup --watch",
|
|
76
|
+
"type-check": "tsc --noEmit",
|
|
77
|
+
"clean": "rm -rf dist",
|
|
78
|
+
"test": "vitest run --passWithNoTests",
|
|
79
|
+
"test:watch": "vitest",
|
|
80
|
+
"test:coverage": "vitest run --coverage"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { UrlboxPlugin } from '../urlbox.plugin.js';
|
|
3
|
+
import type { PluginContext, ScreenshotOptions } from '@ever-works/plugin';
|
|
4
|
+
|
|
5
|
+
// Mock the urlbox module
|
|
6
|
+
// urlbox exports a factory function: Urlbox(apiKey, apiSecret) => { generateRenderLink(options) }
|
|
7
|
+
vi.mock('urlbox', () => {
|
|
8
|
+
const mockGenerateRenderLink = vi
|
|
9
|
+
.fn()
|
|
10
|
+
.mockReturnValue('https://api.urlbox.com/v1/render?url=https%3A%2F%2Fexample.com&width=1280');
|
|
11
|
+
|
|
12
|
+
const mockUrlboxFactory = vi.fn().mockReturnValue({
|
|
13
|
+
generateRenderLink: mockGenerateRenderLink
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
default: mockUrlboxFactory
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Mock global fetch
|
|
22
|
+
const mockFetchResponse = {
|
|
23
|
+
ok: true,
|
|
24
|
+
status: 200,
|
|
25
|
+
arrayBuffer: vi.fn().mockResolvedValue(new ArrayBuffer(100))
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockFetchResponse));
|
|
29
|
+
|
|
30
|
+
describe('UrlboxPlugin', () => {
|
|
31
|
+
let plugin: UrlboxPlugin;
|
|
32
|
+
let mockContext: PluginContext;
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
plugin = new UrlboxPlugin();
|
|
36
|
+
mockContext = {
|
|
37
|
+
pluginId: 'urlbox',
|
|
38
|
+
logger: {
|
|
39
|
+
log: vi.fn(),
|
|
40
|
+
error: vi.fn(),
|
|
41
|
+
warn: vi.fn(),
|
|
42
|
+
debug: vi.fn()
|
|
43
|
+
},
|
|
44
|
+
cache: {} as any,
|
|
45
|
+
http: {} as any,
|
|
46
|
+
env: {} as any,
|
|
47
|
+
envVars: {} as any,
|
|
48
|
+
services: {} as any,
|
|
49
|
+
getSettings: vi.fn().mockResolvedValue({}),
|
|
50
|
+
getResolvedSettings: vi.fn().mockResolvedValue({}),
|
|
51
|
+
onEvent: vi.fn(),
|
|
52
|
+
emitEvent: vi.fn(),
|
|
53
|
+
registerCustomCapability: vi.fn(),
|
|
54
|
+
getCustomCapability: vi.fn(),
|
|
55
|
+
hasCustomCapability: vi.fn(),
|
|
56
|
+
listCustomCapabilities: vi.fn()
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
vi.clearAllMocks();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('Plugin Metadata', () => {
|
|
65
|
+
it('should have correct plugin metadata', () => {
|
|
66
|
+
expect(plugin.id).toBe('urlbox');
|
|
67
|
+
expect(plugin.name).toBe('Urlbox');
|
|
68
|
+
expect(plugin.version).toBe('1.0.0');
|
|
69
|
+
expect(plugin.category).toBe('screenshot');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should have screenshot capability', () => {
|
|
73
|
+
expect(plugin.capabilities).toContain('screenshot');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should have provider name', () => {
|
|
77
|
+
expect(plugin.providerName).toBe('Urlbox');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should have hybrid configuration mode', () => {
|
|
81
|
+
expect(plugin.configurationMode).toBe('hybrid');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('Settings Schema', () => {
|
|
86
|
+
it('should define apiKey as required', () => {
|
|
87
|
+
expect(plugin.settingsSchema.required).toContain('apiKey');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should mark apiKey as secret', () => {
|
|
91
|
+
const properties = plugin.settingsSchema.properties as Record<string, any>;
|
|
92
|
+
expect(properties.apiKey['x-secret']).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should mark apiSecret as secret', () => {
|
|
96
|
+
const properties = plugin.settingsSchema.properties as Record<string, any>;
|
|
97
|
+
expect(properties.apiSecret['x-secret']).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should have environment variable fallbacks', () => {
|
|
101
|
+
const properties = plugin.settingsSchema.properties as Record<string, any>;
|
|
102
|
+
expect(properties.apiKey['x-envVar']).toBe('PLUGIN_URLBOX_API_KEY');
|
|
103
|
+
expect(properties.apiSecret['x-envVar']).toBe('PLUGIN_URLBOX_API_SECRET');
|
|
104
|
+
expect(properties.viewportWidth['x-envVar']).toBe('PLUGIN_URLBOX_VIEWPORT_WIDTH');
|
|
105
|
+
expect(properties.viewportHeight['x-envVar']).toBe('PLUGIN_URLBOX_VIEWPORT_HEIGHT');
|
|
106
|
+
expect(properties.format['x-envVar']).toBe('PLUGIN_URLBOX_FORMAT');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should have default values for viewport settings', () => {
|
|
110
|
+
const properties = plugin.settingsSchema.properties as Record<string, any>;
|
|
111
|
+
expect(properties.viewportWidth.default).toBe(1280);
|
|
112
|
+
expect(properties.viewportHeight.default).toBe(1024);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should have default values for format and blocking options', () => {
|
|
116
|
+
const properties = plugin.settingsSchema.properties as Record<string, any>;
|
|
117
|
+
expect(properties.format.default).toBe('png');
|
|
118
|
+
expect(properties.fullPage.default).toBe(false);
|
|
119
|
+
expect(properties.blockAds.default).toBe(true);
|
|
120
|
+
expect(properties.hideCookieBanners.default).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should have quality setting with correct bounds', () => {
|
|
124
|
+
const properties = plugin.settingsSchema.properties as Record<string, any>;
|
|
125
|
+
expect(properties.quality.default).toBe(80);
|
|
126
|
+
expect(properties.quality.minimum).toBe(1);
|
|
127
|
+
expect(properties.quality.maximum).toBe(100);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should have retina setting defaulting to false', () => {
|
|
131
|
+
const properties = plugin.settingsSchema.properties as Record<string, any>;
|
|
132
|
+
expect(properties.retina.default).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should have format enum with supported formats', () => {
|
|
136
|
+
const properties = plugin.settingsSchema.properties as Record<string, any>;
|
|
137
|
+
expect(properties.format.enum).toEqual(['png', 'jpg', 'jpeg', 'webp']);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('IScreenshotPlugin - capture', () => {
|
|
142
|
+
it('should capture screenshot successfully with settings', async () => {
|
|
143
|
+
await plugin.onLoad(mockContext);
|
|
144
|
+
|
|
145
|
+
const options: ScreenshotOptions = {
|
|
146
|
+
url: 'https://example.com',
|
|
147
|
+
settings: {
|
|
148
|
+
apiKey: 'test-api-key',
|
|
149
|
+
apiSecret: 'test-api-secret'
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const result = await plugin.capture(options);
|
|
154
|
+
|
|
155
|
+
expect(result.success).toBe(true);
|
|
156
|
+
expect(result.imageBuffer).toBeDefined();
|
|
157
|
+
expect(result.imageBase64).toBeDefined();
|
|
158
|
+
expect(result.imageUrl).toBeDefined();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should fail when API key is not configured', async () => {
|
|
162
|
+
await plugin.onLoad(mockContext);
|
|
163
|
+
|
|
164
|
+
const options: ScreenshotOptions = {
|
|
165
|
+
url: 'https://example.com',
|
|
166
|
+
settings: {}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const result = await plugin.capture(options);
|
|
170
|
+
|
|
171
|
+
expect(result.success).toBe(false);
|
|
172
|
+
expect(result.error).toContain('API key not configured');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should use provided viewport dimensions', async () => {
|
|
176
|
+
await plugin.onLoad(mockContext);
|
|
177
|
+
|
|
178
|
+
const options: ScreenshotOptions = {
|
|
179
|
+
url: 'https://example.com',
|
|
180
|
+
viewportWidth: 1920,
|
|
181
|
+
viewportHeight: 1080,
|
|
182
|
+
settings: {
|
|
183
|
+
apiKey: 'test-key'
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const result = await plugin.capture(options);
|
|
188
|
+
|
|
189
|
+
expect(result.success).toBe(true);
|
|
190
|
+
expect(result.width).toBe(1920);
|
|
191
|
+
expect(result.height).toBe(1080);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('IScreenshotPlugin - getScreenshotUrl', () => {
|
|
196
|
+
it('should generate URL with settings', async () => {
|
|
197
|
+
await plugin.onLoad(mockContext);
|
|
198
|
+
|
|
199
|
+
const options: ScreenshotOptions = {
|
|
200
|
+
url: 'https://example.com',
|
|
201
|
+
settings: {
|
|
202
|
+
apiKey: 'test-key'
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const url = await plugin.getScreenshotUrl(options);
|
|
207
|
+
|
|
208
|
+
expect(url).toBeDefined();
|
|
209
|
+
expect(url).toContain('api.urlbox.com');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should generate render link with API secret', async () => {
|
|
213
|
+
await plugin.onLoad(mockContext);
|
|
214
|
+
|
|
215
|
+
const options: ScreenshotOptions = {
|
|
216
|
+
url: 'https://example.com',
|
|
217
|
+
settings: {
|
|
218
|
+
apiKey: 'test-key',
|
|
219
|
+
apiSecret: 'test-secret'
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const url = await plugin.getScreenshotUrl(options);
|
|
224
|
+
|
|
225
|
+
expect(url).toBeDefined();
|
|
226
|
+
expect(url).toContain('api.urlbox.com');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should return null when API key is missing', async () => {
|
|
230
|
+
await plugin.onLoad(mockContext);
|
|
231
|
+
|
|
232
|
+
const options: ScreenshotOptions = {
|
|
233
|
+
url: 'https://example.com',
|
|
234
|
+
settings: {}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const url = await plugin.getScreenshotUrl(options);
|
|
238
|
+
|
|
239
|
+
expect(url).toBeNull();
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe('IScreenshotPlugin - isAvailable', () => {
|
|
244
|
+
it('should return true when API key is configured', async () => {
|
|
245
|
+
(mockContext.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({ apiKey: 'test-key' });
|
|
246
|
+
await plugin.onLoad(mockContext);
|
|
247
|
+
const available = await plugin.isAvailable();
|
|
248
|
+
expect(available).toBe(true);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should return false when not loaded', async () => {
|
|
252
|
+
const available = await plugin.isAvailable();
|
|
253
|
+
expect(available).toBe(false);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('IScreenshotPlugin - validateCredentials', () => {
|
|
258
|
+
it('should return invalid when plugin not initialized', async () => {
|
|
259
|
+
const result = await plugin.validateCredentials();
|
|
260
|
+
|
|
261
|
+
expect(result.valid).toBe(false);
|
|
262
|
+
expect(result.message).toContain('not initialized');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should return invalid when API key not configured', async () => {
|
|
266
|
+
vi.mocked(mockContext.getSettings).mockResolvedValue({});
|
|
267
|
+
await plugin.onLoad(mockContext);
|
|
268
|
+
|
|
269
|
+
const result = await plugin.validateCredentials();
|
|
270
|
+
|
|
271
|
+
expect(result.valid).toBe(false);
|
|
272
|
+
expect(result.message).toContain('API key is not configured');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should return valid when credentials are configured', async () => {
|
|
276
|
+
vi.mocked(mockContext.getSettings).mockResolvedValue({
|
|
277
|
+
apiKey: 'test-key'
|
|
278
|
+
});
|
|
279
|
+
await plugin.onLoad(mockContext);
|
|
280
|
+
|
|
281
|
+
const result = await plugin.validateCredentials();
|
|
282
|
+
|
|
283
|
+
expect(result.valid).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('IScreenshotPlugin - getSupportedFormats', () => {
|
|
288
|
+
it('should return supported formats', () => {
|
|
289
|
+
const formats = plugin.getSupportedFormats();
|
|
290
|
+
|
|
291
|
+
expect(formats).toContain('png');
|
|
292
|
+
expect(formats).toContain('jpg');
|
|
293
|
+
expect(formats).toContain('jpeg');
|
|
294
|
+
expect(formats).toContain('webp');
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('IScreenshotPlugin - getMaxDimensions', () => {
|
|
299
|
+
it('should return maximum viewport dimensions', () => {
|
|
300
|
+
const dimensions = plugin.getMaxDimensions();
|
|
301
|
+
|
|
302
|
+
expect(dimensions.width).toBe(3840);
|
|
303
|
+
expect(dimensions.height).toBe(2160);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe('Lifecycle', () => {
|
|
308
|
+
it('should log on load', async () => {
|
|
309
|
+
await plugin.onLoad(mockContext);
|
|
310
|
+
expect(mockContext.logger.log).toHaveBeenCalledWith('Urlbox Plugin loaded');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('should clear context on unload', async () => {
|
|
314
|
+
await plugin.onLoad(mockContext);
|
|
315
|
+
await plugin.onUnload();
|
|
316
|
+
// Context should be cleared - validateCredentials should fail
|
|
317
|
+
const result = await plugin.validateCredentials();
|
|
318
|
+
expect(result.valid).toBe(false);
|
|
319
|
+
expect(result.message).toContain('not initialized');
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
describe('healthCheck', () => {
|
|
324
|
+
it('should return healthy status', async () => {
|
|
325
|
+
const health = await plugin.healthCheck();
|
|
326
|
+
|
|
327
|
+
expect(health.status).toBe('healthy');
|
|
328
|
+
expect(health.checkedAt).toBeDefined();
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('getManifest', () => {
|
|
333
|
+
it('should return correct manifest', () => {
|
|
334
|
+
const manifest = plugin.getManifest();
|
|
335
|
+
|
|
336
|
+
expect(manifest.id).toBe('urlbox');
|
|
337
|
+
expect(manifest.name).toBe('Urlbox');
|
|
338
|
+
expect(manifest.version).toBe('1.0.0');
|
|
339
|
+
expect(manifest.category).toBe('screenshot');
|
|
340
|
+
expect(manifest.capabilities).toContain('screenshot');
|
|
341
|
+
expect(manifest.builtIn).toBe(false);
|
|
342
|
+
expect(manifest.systemPlugin).toBe(false);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should have icon configuration', () => {
|
|
346
|
+
const manifest = plugin.getManifest();
|
|
347
|
+
|
|
348
|
+
expect(manifest.icon).toBeDefined();
|
|
349
|
+
expect(manifest.icon?.type).toBe('url');
|
|
350
|
+
expect(manifest.icon?.value).toBe('https://urlbox.com/apple-touch-icon.png');
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
});
|
package/src/index.ts
ADDED