iobroker.foobar2000 2.2.0 → 2.3.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.
@@ -0,0 +1,809 @@
1
+ # ioBroker Adapter Development with GitHub Copilot
2
+
3
+ **Version:** 0.5.7
4
+ **Template Source:** https://github.com/DrozmotiX/ioBroker-Copilot-Instructions
5
+
6
+ This file contains instructions and best practices for GitHub Copilot when working on ioBroker adapter development.
7
+
8
+ ---
9
+
10
+ ## 📑 Table of Contents
11
+
12
+ 1. [Project Context](#project-context)
13
+ 2. [Code Quality & Standards](#code-quality--standards)
14
+ - [Code Style Guidelines](#code-style-guidelines)
15
+ - [ESLint Configuration](#eslint-configuration)
16
+ 3. [Testing](#testing)
17
+ - [Unit Testing](#unit-testing)
18
+ - [Integration Testing](#integration-testing)
19
+ - [API Testing with Credentials](#api-testing-with-credentials)
20
+ 4. [Development Best Practices](#development-best-practices)
21
+ - [Dependency Management](#dependency-management)
22
+ - [HTTP Client Libraries](#http-client-libraries)
23
+ - [Error Handling](#error-handling)
24
+ 5. [Admin UI Configuration](#admin-ui-configuration)
25
+ - [JSON-Config Setup](#json-config-setup)
26
+ - [Translation Management](#translation-management)
27
+ 6. [Documentation](#documentation)
28
+ - [README Updates](#readme-updates)
29
+ - [Changelog Management](#changelog-management)
30
+ 7. [CI/CD & GitHub Actions](#cicd--github-actions)
31
+ - [Workflow Configuration](#workflow-configuration)
32
+ - [Testing Integration](#testing-integration)
33
+
34
+ ---
35
+
36
+ ## Project Context
37
+
38
+ You are working on an ioBroker adapter. ioBroker is an integration platform for the Internet of Things, focused on building smart home and industrial IoT solutions. Adapters are plugins that connect ioBroker to external systems, devices, or services.
39
+
40
+ ### Adapter-Specific Context: Foobar2000 Player Control
41
+
42
+ This is the **ioBroker.foobar2000** adapter - a multimedia adapter that provides control interface for the Foobar2000 audio player. Key characteristics:
43
+
44
+ - **Primary Function**: Controls Foobar2000 media player via HTTP API using the foo_httpcontrol plugin
45
+ - **Communication Method**: HTTP GET requests to Foobar2000's web interface (default port 8888)
46
+ - **Key Features**: Playback control, volume management, playlist handling, track information, album art display
47
+ - **Operation Modes**: Local (direct file system access) and Remote (network-based control)
48
+ - **State Polling**: Regular HTTP polling to maintain current player state
49
+ - **Media States**: Comprehensive media player state management (play/pause/stop, seek, shuffle, repeat)
50
+
51
+ ### Required External Components
52
+
53
+ - **foo_httpcontrol Plugin**: Must be installed in Foobar2000 for HTTP API access
54
+ - **Configuration Note**: Album art requires `albumart_prefer_embedded = 0` in foobar2000controller config
55
+
56
+ ---
57
+
58
+ ## Code Quality & Standards
59
+
60
+ ### Code Style Guidelines
61
+
62
+ - Follow JavaScript/TypeScript best practices
63
+ - Use async/await for asynchronous operations
64
+ - Implement proper resource cleanup in `unload()` method
65
+ - Use semantic versioning for adapter releases
66
+ - Include proper JSDoc comments for public methods
67
+
68
+ **Timer and Resource Cleanup Example:**
69
+ ```javascript
70
+ private connectionTimer?: NodeJS.Timeout;
71
+
72
+ async onReady() {
73
+ this.connectionTimer = setInterval(() => this.checkConnection(), 30000);
74
+ }
75
+
76
+ onUnload(callback) {
77
+ try {
78
+ if (this.connectionTimer) {
79
+ clearInterval(this.connectionTimer);
80
+ this.connectionTimer = undefined;
81
+ }
82
+ callback();
83
+ } catch (e) {
84
+ callback();
85
+ }
86
+ }
87
+ ```
88
+
89
+ ### ESLint Configuration
90
+
91
+ **CRITICAL:** ESLint validation must run FIRST in your CI/CD pipeline, before any other tests. This "lint-first" approach catches code quality issues early.
92
+
93
+ #### Setup
94
+ ```bash
95
+ npm install --save-dev eslint @iobroker/eslint-config
96
+ ```
97
+
98
+ #### Configuration (.eslintrc.json)
99
+ ```json
100
+ {
101
+ "extends": "@iobroker/eslint-config",
102
+ "rules": {
103
+ // Add project-specific rule overrides here if needed
104
+ }
105
+ }
106
+ ```
107
+
108
+ #### Package.json Scripts
109
+ ```json
110
+ {
111
+ "scripts": {
112
+ "lint": "eslint --max-warnings 0 .",
113
+ "lint:fix": "eslint . --fix"
114
+ }
115
+ }
116
+ ```
117
+
118
+ #### Best Practices
119
+ 1. ✅ Run ESLint before committing — fix ALL warnings, not just errors
120
+ 2. ✅ Use `lint:fix` for auto-fixable issues
121
+ 3. ✅ Don't disable rules without documentation
122
+ 4. ✅ Lint all relevant files (main code, tests, build scripts)
123
+ 5. ✅ Keep `@iobroker/eslint-config` up to date
124
+ 6. ✅ **ESLint warnings are treated as errors in CI** (`--max-warnings 0`). The `lint` script above already includes this flag — run `npm run lint` to match CI behavior locally
125
+
126
+ #### Common Issues
127
+ - **Unused variables**: Remove or prefix with underscore (`_variable`)
128
+ - **Missing semicolons**: Run `npm run lint:fix`
129
+ - **Indentation**: Use 4 spaces (ioBroker standard)
130
+ - **console.log**: Replace with `adapter.log.debug()` or remove
131
+
132
+ ---
133
+
134
+ ## Testing
135
+
136
+ ### Unit Testing
137
+
138
+ - Use Jest as the primary testing framework
139
+ - Create tests for all adapter main functions and helper methods
140
+ - Test error handling scenarios and edge cases
141
+ - Mock external API calls and hardware dependencies
142
+ - For adapters connecting to APIs/devices not reachable by internet, provide example data files
143
+
144
+ **Example Structure:**
145
+ ```javascript
146
+ describe('AdapterName', () => {
147
+ let adapter;
148
+
149
+ beforeEach(() => {
150
+ // Setup test adapter instance
151
+ });
152
+
153
+ test('should initialize correctly', () => {
154
+ // Test adapter initialization
155
+ });
156
+ });
157
+ ```
158
+
159
+ ### Integration Testing
160
+
161
+ **CRITICAL:** Use the official `@iobroker/testing` framework. This is the ONLY correct way to test ioBroker adapters.
162
+
163
+ **Official Documentation:** https://github.com/ioBroker/testing
164
+
165
+ #### Framework Structure
166
+
167
+ **✅ Correct Pattern:**
168
+ ```javascript
169
+ const path = require('path');
170
+ const { tests } = require('@iobroker/testing');
171
+
172
+ tests.integration(path.join(__dirname, '..'), {
173
+ defineAdditionalTests({ suite }) {
174
+ suite('Test adapter with specific configuration', (getHarness) => {
175
+ let harness;
176
+
177
+ before(() => {
178
+ harness = getHarness();
179
+ });
180
+
181
+ it('should configure and start adapter', function () {
182
+ return new Promise(async (resolve, reject) => {
183
+ try {
184
+ // Get adapter object
185
+ const obj = await new Promise((res, rej) => {
186
+ harness.objects.getObject('system.adapter.your-adapter.0', (err, o) => {
187
+ if (err) return rej(err);
188
+ res(o);
189
+ });
190
+ });
191
+
192
+ if (!obj) return reject(new Error('Adapter object not found'));
193
+
194
+ // Configure adapter
195
+ Object.assign(obj.native, {
196
+ position: '52.520008,13.404954',
197
+ createHourly: true,
198
+ });
199
+
200
+ harness.objects.setObject(obj._id, obj);
201
+
202
+ // Start and wait
203
+ await harness.startAdapterAndWait();
204
+ await new Promise(resolve => setTimeout(resolve, 15000));
205
+
206
+ // Verify states
207
+ const stateIds = await harness.dbConnection.getStateIDs('your-adapter.0.*');
208
+
209
+ if (stateIds.length > 0) {
210
+ console.log('✅ Adapter successfully created states');
211
+ await harness.stopAdapter();
212
+ resolve(true);
213
+ } else {
214
+ reject(new Error('Adapter did not create any states'));
215
+ }
216
+ } catch (error) {
217
+ reject(error);
218
+ }
219
+ });
220
+ }).timeout(40000);
221
+ });
222
+ }
223
+ });
224
+ ```
225
+
226
+ #### Testing Success AND Failure Scenarios
227
+
228
+ **IMPORTANT:** For every "it works" test, implement corresponding "it fails gracefully" tests.
229
+
230
+ **Failure Scenario Example:**
231
+ ```javascript
232
+ it('should NOT create daily states when daily is disabled', function () {
233
+ return new Promise(async (resolve, reject) => {
234
+ try {
235
+ harness = getHarness();
236
+ const obj = await new Promise((res, rej) => {
237
+ harness.objects.getObject('system.adapter.your-adapter.0', (err, o) => {
238
+ if (err) return rej(err);
239
+ res(o);
240
+ });
241
+ });
242
+
243
+ if (!obj) return reject(new Error('Adapter object not found'));
244
+
245
+ Object.assign(obj.native, {
246
+ createDaily: false, // Daily disabled
247
+ });
248
+
249
+ await new Promise((res, rej) => {
250
+ harness.objects.setObject(obj._id, obj, (err) => {
251
+ if (err) return rej(err);
252
+ res(undefined);
253
+ });
254
+ });
255
+
256
+ await harness.startAdapterAndWait();
257
+ await new Promise((res) => setTimeout(res, 20000));
258
+
259
+ const stateIds = await harness.dbConnection.getStateIDs('your-adapter.0.*');
260
+ const dailyStates = stateIds.filter((key) => key.includes('daily'));
261
+
262
+ if (dailyStates.length === 0) {
263
+ console.log('✅ No daily states found as expected');
264
+ resolve(true);
265
+ } else {
266
+ reject(new Error('Expected no daily states but found some'));
267
+ }
268
+
269
+ await harness.stopAdapter();
270
+ } catch (error) {
271
+ reject(error);
272
+ }
273
+ });
274
+ }).timeout(40000);
275
+ ```
276
+
277
+ #### Key Rules
278
+
279
+ 1. ✅ Use `@iobroker/testing` framework
280
+ 2. ✅ Configure via `harness.objects.setObject()`
281
+ 3. ✅ Start via `harness.startAdapterAndWait()`
282
+ 4. ✅ Verify states via `harness.states.getState()`
283
+ 5. ✅ Allow proper timeouts for async operations
284
+ 6. ❌ NEVER test API URLs directly
285
+ 7. ❌ NEVER bypass the harness system
286
+
287
+ #### Workflow Dependencies
288
+
289
+ Integration tests should run ONLY after lint and adapter tests pass:
290
+
291
+ ```yaml
292
+ integration-tests:
293
+ needs: [check-and-lint, adapter-tests]
294
+ runs-on: ubuntu-22.04
295
+ ```
296
+
297
+ ### API Testing with Credentials
298
+
299
+ For adapters connecting to external APIs requiring authentication:
300
+
301
+ #### Password Encryption for Integration Tests
302
+
303
+ ```javascript
304
+ async function encryptPassword(harness, password) {
305
+ const systemConfig = await harness.objects.getObjectAsync("system.config");
306
+ if (!systemConfig?.native?.secret) {
307
+ throw new Error("Could not retrieve system secret for password encryption");
308
+ }
309
+
310
+ const secret = systemConfig.native.secret;
311
+ let result = '';
312
+ for (let i = 0; i < password.length; ++i) {
313
+ result += String.fromCharCode(secret[i % secret.length].charCodeAt(0) ^ password.charCodeAt(i));
314
+ }
315
+ return result;
316
+ }
317
+ ```
318
+
319
+ #### Demo Credentials Testing Pattern
320
+
321
+ - Use provider demo credentials when available (e.g., `demo@api-provider.com` / `demo`)
322
+ - Create separate test file: `test/integration-demo.js`
323
+ - Add npm script: `"test:integration-demo": "mocha test/integration-demo --exit"`
324
+ - Implement clear success/failure criteria
325
+
326
+ **Example Implementation:**
327
+ ```javascript
328
+ it("Should connect to API with demo credentials", async () => {
329
+ const encryptedPassword = await encryptPassword(harness, "demo_password");
330
+
331
+ await harness.changeAdapterConfig("your-adapter", {
332
+ native: {
333
+ username: "demo@provider.com",
334
+ password: encryptedPassword,
335
+ }
336
+ });
337
+
338
+ await harness.startAdapter();
339
+ await new Promise(resolve => setTimeout(resolve, 60000));
340
+
341
+ const connectionState = await harness.states.getStateAsync("your-adapter.0.info.connection");
342
+
343
+ if (connectionState?.val === true) {
344
+ console.log("✅ SUCCESS: API connection established");
345
+ return true;
346
+ } else {
347
+ throw new Error("API Test Failed: Expected API connection. Check logs for API errors.");
348
+ }
349
+ }).timeout(120000);
350
+ ```
351
+
352
+ ---
353
+
354
+ ## Development Best Practices
355
+
356
+ ### Dependency Management
357
+
358
+ - Always use `npm` for dependency management
359
+ - Use `npm ci` for installing existing dependencies (respects package-lock.json)
360
+ - Use `npm install` only when adding or updating dependencies
361
+ - Keep dependencies minimal and focused
362
+ - Only update dependencies in separate Pull Requests
363
+
364
+ **When modifying package.json:**
365
+ 1. Run `npm install` to sync package-lock.json
366
+ 2. Commit both package.json and package-lock.json together
367
+
368
+ **Best Practices:**
369
+ - Prefer built-in Node.js modules when possible
370
+ - Use `@iobroker/adapter-core` for adapter base functionality
371
+ - Avoid deprecated packages
372
+ - Document specific version requirements
373
+
374
+ ### HTTP Client Libraries
375
+
376
+ - **Preferred:** Use native `fetch` API (Node.js 20+ required)
377
+ - **Avoid:** `axios` unless specific features are required
378
+
379
+ **Example with fetch:**
380
+ ```javascript
381
+ try {
382
+ const response = await fetch('https://api.example.com/data');
383
+ if (!response.ok) {
384
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
385
+ }
386
+ const data = await response.json();
387
+ } catch (error) {
388
+ this.log.error(`API request failed: ${error.message}`);
389
+ }
390
+ ```
391
+
392
+ **Other Recommendations:**
393
+ - **Logging:** Use adapter built-in logging (`this.log.*`)
394
+ - **Scheduling:** Use adapter built-in timers and intervals
395
+ - **File operations:** Use Node.js `fs/promises`
396
+ - **Configuration:** Use adapter config system
397
+
398
+ ### Error Handling
399
+
400
+ - Always catch and log errors appropriately
401
+ - Use adapter log levels (error, warn, info, debug)
402
+ - Provide meaningful, user-friendly error messages
403
+ - Handle network failures gracefully
404
+ - Implement retry mechanisms where appropriate
405
+ - Always clean up timers, intervals, and resources in `unload()` method
406
+
407
+ **Example:**
408
+ ```javascript
409
+ try {
410
+ await this.connectToDevice();
411
+ } catch (error) {
412
+ this.log.error(`Failed to connect to device: ${error.message}`);
413
+ this.setState('info.connection', false, true);
414
+ // Implement retry logic if needed
415
+ }
416
+ ```
417
+
418
+ ---
419
+
420
+ ## Admin UI Configuration
421
+
422
+ ### JSON-Config Setup
423
+
424
+ Use JSON-Config format for modern ioBroker admin interfaces.
425
+
426
+ **Example Structure:**
427
+ ```json
428
+ {
429
+ "type": "panel",
430
+ "items": {
431
+ "host": {
432
+ "type": "text",
433
+ "label": "Host address",
434
+ "help": "IP address or hostname of the device"
435
+ }
436
+ }
437
+ }
438
+ ```
439
+
440
+ **Guidelines:**
441
+ - ✅ Use consistent naming conventions
442
+ - ✅ Provide sensible default values
443
+ - ✅ Include validation for required fields
444
+ - ✅ Add tooltips for complex options
445
+ - ✅ Ensure translations for all supported languages (minimum English and German)
446
+ - ✅ Write end-user friendly labels, avoid technical jargon
447
+
448
+ ### Translation Management
449
+
450
+ **CRITICAL:** Translation files must stay synchronized with `admin/jsonConfig.json`. Orphaned keys or missing translations cause UI issues and PR review delays.
451
+
452
+ #### Overview
453
+ - **Location:** `admin/i18n/{lang}/translations.json` for 11 languages (de, en, es, fr, it, nl, pl, pt, ru, uk, zh-cn)
454
+ - **Source of truth:** `admin/jsonConfig.json` - all `label` and `help` properties must have translations
455
+ - **Command:** `npm run translate` - auto-generates translations but does NOT remove orphaned keys
456
+ - **Formatting:** English uses tabs, other languages use 4 spaces
457
+
458
+ #### Critical Rules
459
+ 1. ✅ Keys must match exactly with jsonConfig.json
460
+ 2. ✅ No orphaned keys in translation files
461
+ 3. ✅ All translations must be in native language (no English fallbacks)
462
+ 4. ✅ Keys must be sorted alphabetically
463
+
464
+ #### Workflow for Translation Updates
465
+
466
+ **When modifying admin/jsonConfig.json:**
467
+
468
+ 1. Make your changes to labels/help texts
469
+ 2. Run automatic translation: `npm run translate`
470
+ 3. Create validation script (`scripts/validate-translations.js`):
471
+
472
+ ```javascript
473
+ const fs = require('fs');
474
+ const path = require('path');
475
+ const jsonConfig = JSON.parse(fs.readFileSync('admin/jsonConfig.json', 'utf8'));
476
+
477
+ function extractTexts(obj, texts = new Set()) {
478
+ if (typeof obj === 'object' && obj !== null) {
479
+ if (obj.label) texts.add(obj.label);
480
+ if (obj.help) texts.add(obj.help);
481
+ for (const key in obj) {
482
+ extractTexts(obj[key], texts);
483
+ }
484
+ }
485
+ return texts;
486
+ }
487
+
488
+ const requiredTexts = extractTexts(jsonConfig);
489
+ const languages = ['de', 'en', 'es', 'fr', 'it', 'nl', 'pl', 'pt', 'ru', 'uk', 'zh-cn'];
490
+ let hasErrors = false;
491
+
492
+ languages.forEach(lang => {
493
+ const translationPath = path.join('admin', 'i18n', lang, 'translations.json');
494
+ const translations = JSON.parse(fs.readFileSync(translationPath, 'utf8'));
495
+ const translationKeys = new Set(Object.keys(translations));
496
+
497
+ const missing = Array.from(requiredTexts).filter(text => !translationKeys.has(text));
498
+ const orphaned = Array.from(translationKeys).filter(key => !requiredTexts.has(key));
499
+
500
+ console.log(`\n=== ${lang} ===`);
501
+ if (missing.length > 0) {
502
+ console.error('❌ Missing keys:', missing);
503
+ hasErrors = true;
504
+ }
505
+ if (orphaned.length > 0) {
506
+ console.error('❌ Orphaned keys (REMOVE THESE):', orphaned);
507
+ hasErrors = true;
508
+ }
509
+ if (missing.length === 0 && orphaned.length === 0) {
510
+ console.log('✅ All keys match!');
511
+ }
512
+ });
513
+
514
+ process.exit(hasErrors ? 1 : 0);
515
+ ```
516
+
517
+ 4. Run validation: `node scripts/validate-translations.js`
518
+ 5. Remove orphaned keys manually from all translation files
519
+ 6. Add missing translations in native languages
520
+ 7. Run: `npm run lint && npm run test`
521
+
522
+ #### Add Validation to package.json
523
+
524
+ ```json
525
+ {
526
+ "scripts": {
527
+ "translate": "translate-adapter",
528
+ "validate:translations": "node scripts/validate-translations.js",
529
+ "pretest": "npm run lint && npm run validate:translations"
530
+ }
531
+ }
532
+ ```
533
+
534
+ #### Translation Checklist
535
+
536
+ Before committing changes to admin UI or translations:
537
+ 1. ✅ Validation script shows "All keys match!" for all 11 languages
538
+ 2. ✅ No orphaned keys in any translation file
539
+ 3. ✅ All translations in native language
540
+ 4. ✅ Keys alphabetically sorted
541
+ 5. ✅ `npm run lint` passes
542
+ 6. ✅ `npm run test` passes
543
+ 7. ✅ Admin UI displays correctly
544
+
545
+ ---
546
+
547
+ ## Documentation
548
+
549
+ ### README Updates
550
+
551
+ #### Required Sections
552
+ 1. **Installation** - Clear npm/ioBroker admin installation steps
553
+ 2. **Configuration** - Detailed configuration options with examples
554
+ 3. **Usage** - Practical examples and use cases
555
+ 4. **Changelog** - Version history (use "## **WORK IN PROGRESS**" for ongoing changes)
556
+ 5. **License** - License information (typically MIT for ioBroker adapters)
557
+ 6. **Support** - Links to issues, discussions, community support
558
+
559
+ #### Documentation Standards
560
+ - Use clear, concise language
561
+ - Include code examples for configuration
562
+ - Add screenshots for admin interface when applicable
563
+ - Maintain multilingual support (minimum English and German)
564
+ - Always reference issues in commits and PRs (e.g., "fixes #xx")
565
+
566
+ #### Mandatory README Updates for PRs
567
+
568
+ For **every PR or new feature**, always add a user-friendly entry to README.md:
569
+
570
+ - Add entries under `## **WORK IN PROGRESS**` section
571
+ - Use format: `* (author) **TYPE**: Description of user-visible change`
572
+ - Types: **NEW** (features), **FIXED** (bugs), **ENHANCED** (improvements), **TESTING** (test additions), **CI/CD** (automation)
573
+ - Focus on user impact, not technical details
574
+
575
+ **Example:**
576
+ ```markdown
577
+ ## **WORK IN PROGRESS**
578
+
579
+ * (DutchmanNL) **FIXED**: Adapter now properly validates login credentials (fixes #25)
580
+ * (DutchmanNL) **NEW**: Added device discovery to simplify initial setup
581
+ ```
582
+
583
+ ### Changelog Management
584
+
585
+ Follow the [AlCalzone release-script](https://github.com/AlCalzone/release-script) standard.
586
+
587
+ #### Format Requirements
588
+
589
+ ```markdown
590
+ # Changelog
591
+
592
+ <!--
593
+ Placeholder for the next version (at the beginning of the line):
594
+ ## **WORK IN PROGRESS**
595
+ -->
596
+
597
+ ## **WORK IN PROGRESS**
598
+
599
+ - (author) **NEW**: Added new feature X
600
+ - (author) **FIXED**: Fixed bug Y (fixes #25)
601
+
602
+ ## v0.1.0 (2023-01-01)
603
+ Initial release
604
+ ```
605
+
606
+ #### Workflow Process
607
+ - **During Development:** All changes go under `## **WORK IN PROGRESS**`
608
+ - **For Every PR:** Add user-facing changes to WORK IN PROGRESS section
609
+ - **Before Merge:** Version number and date added when merging to main
610
+ - **Release Process:** Release-script automatically converts placeholder to actual version
611
+
612
+ #### Change Entry Format
613
+ - Format: `- (author) **TYPE**: User-friendly description`
614
+ - Types: **NEW**, **FIXED**, **ENHANCED**
615
+ - Focus on user impact, not technical implementation
616
+ - Reference issues: "fixes #XX" or "solves #XX"
617
+
618
+ ---
619
+
620
+ ## CI/CD & GitHub Actions
621
+
622
+ ### Workflow Configuration
623
+
624
+ #### GitHub Actions Best Practices
625
+
626
+ **Must use ioBroker official testing actions:**
627
+ - `ioBroker/testing-action-check@v1` for lint and package validation
628
+ - `ioBroker/testing-action-adapter@v1` for adapter tests
629
+ - `ioBroker/testing-action-deploy@v1` for automated releases with Trusted Publishing (OIDC)
630
+
631
+ **Configuration:**
632
+ - **Node.js versions:** Test on 20.x, 22.x, 24.x
633
+ - **Platform:** Use ubuntu-22.04
634
+ - **Automated releases:** Deploy to npm on version tags (requires NPM Trusted Publishing)
635
+ - **Monitoring:** Include Sentry release tracking for error monitoring
636
+
637
+ #### Critical: Lint-First Validation Workflow
638
+
639
+ **ALWAYS run ESLint checks BEFORE other tests.** Benefits:
640
+ - Catches code quality issues immediately
641
+ - Prevents wasting CI resources on tests that would fail due to linting errors
642
+ - Provides faster feedback to developers
643
+ - Enforces consistent code quality
644
+
645
+ **Workflow Dependency Configuration:**
646
+ ```yaml
647
+ jobs:
648
+ check-and-lint:
649
+ # Runs ESLint and package validation
650
+ # Uses: ioBroker/testing-action-check@v1
651
+
652
+ adapter-tests:
653
+ needs: [check-and-lint] # Wait for linting to pass
654
+ # Run adapter unit tests
655
+
656
+ integration-tests:
657
+ needs: [check-and-lint, adapter-tests] # Wait for both
658
+ # Run integration tests
659
+ ```
660
+
661
+ **Key Points:**
662
+ - The `check-and-lint` job has NO dependencies - runs first
663
+ - ALL other test jobs MUST list `check-and-lint` in their `needs` array
664
+ - If linting fails, no other tests run, saving time
665
+ - Fix all ESLint errors before proceeding
666
+
667
+ ### Testing Integration
668
+
669
+ #### API Testing in CI/CD
670
+
671
+ For adapters with external API dependencies:
672
+
673
+ ```yaml
674
+ demo-api-tests:
675
+ if: contains(github.event.head_commit.message, '[skip ci]') == false
676
+ runs-on: ubuntu-22.04
677
+
678
+ steps:
679
+ - name: Checkout code
680
+ uses: actions/checkout@v4
681
+
682
+ - name: Use Node.js 20.x
683
+ uses: actions/setup-node@v4
684
+ with:
685
+ node-version: 20.x
686
+ cache: 'npm'
687
+
688
+ - name: Install dependencies
689
+ run: npm ci
690
+
691
+ - name: Run demo API tests
692
+ run: npm run test:integration-demo
693
+ ```
694
+
695
+ #### Testing Best Practices
696
+ - Run credential tests separately from main test suite
697
+ - Don't make credential tests required for deployment
698
+ - Provide clear failure messages for API issues
699
+ - Use appropriate timeouts for external calls (120+ seconds)
700
+
701
+ #### Package.json Integration
702
+ ```json
703
+ {
704
+ "scripts": {
705
+ "test:integration-demo": "mocha test/integration-demo --exit"
706
+ }
707
+ }
708
+ ```
709
+
710
+ ---
711
+
712
+ ## Adapter-Specific Patterns
713
+
714
+ ### Foobar2000 HTTP API Communication
715
+
716
+ The adapter communicates with Foobar2000 via HTTP GET requests to the foo_httpcontrol plugin. Use the following patterns:
717
+
718
+ ```javascript
719
+ // HTTP command execution pattern
720
+ async executeCommand(command, param) {
721
+ try {
722
+ const response = await this.httpGet(command, [param]);
723
+ if (response && response.data) {
724
+ return response.data;
725
+ }
726
+ } catch (error) {
727
+ this.log.error(`Command ${command} failed: ${error.message}`);
728
+ this.setStateChanged('info.connection', false, true);
729
+ }
730
+ }
731
+
732
+ // Media state update pattern
733
+ updateMediaState(playerData) {
734
+ if (playerData.isplaying) {
735
+ this.setState('state', 'play', true);
736
+ } else if (playerData.ispaused) {
737
+ this.setState('state', 'pause', true);
738
+ } else {
739
+ this.setState('state', 'stop', true);
740
+ }
741
+ }
742
+ ```
743
+
744
+ - Implement connection retry logic with exponential backoff
745
+ - Handle network timeouts gracefully (default 5 seconds)
746
+ - Parse JSON responses safely with error handling
747
+ - Cache frequently accessed data (like playlist information)
748
+ - Validate HTTP response status codes before processing
749
+
750
+ ### Adapter Configuration Structure
751
+
752
+ ```javascript
753
+ // Native configuration object structure
754
+ {
755
+ ip: "127.0.0.1", // Foobar2000 server IP
756
+ port: 8888, // HTTP control port
757
+ login: "", // Optional authentication
758
+ password: "", // Optional password
759
+ path: "C:/Program Files (x86)/foobar2000/", // Local installation path
760
+ cmdstart: "", // Start command for local mode
761
+ cmdexit: "" // Exit command for local mode
762
+ }
763
+ ```
764
+
765
+ - Detect local vs remote operation mode based on IP address
766
+ - Handle authentication if credentials are provided
767
+ - Validate connection parameters on adapter start
768
+ - Implement connection health checks
769
+
770
+ ### State Definitions and Media Controls
771
+
772
+ **Core Media States:**
773
+ - `state`: Current playback state (play/pause/stop/next/previous)
774
+ - `volume`: Volume level (0-100%)
775
+ - `mute`: Mute state (boolean)
776
+ - `seek`: Playback position (0-100%)
777
+
778
+ **Track Information States:**
779
+ - `title`: Current track title
780
+ - `artist`: Current track artist
781
+ - `album`: Current track album
782
+ - `albumArt`: Album art URL or file path
783
+
784
+ **Playlist and Control States:**
785
+ - `playlist`: Current playlist data (JSON string)
786
+ - `itemPlaying`: Currently playing track number
787
+ - `repeat`: Repeat mode (Off/All/One)
788
+ - `shuffle`: Shuffle state (boolean)
789
+
790
+ **Parameter bounds validation:**
791
+ - Volume: 0-100
792
+ - Seek: 0-100%
793
+
794
+ ### Foobar2000 Specific Error Scenarios
795
+
796
+ - Player not running or foo_httpcontrol plugin not installed
797
+ - Invalid command parameters or unsupported operations
798
+ - File system access errors in local mode
799
+ - Playlist or track access permissions
800
+ - Album art requires `albumart_prefer_embedded = 0` in foobar2000controller config
801
+
802
+ ### Test Coverage Areas for Foobar2000 Adapter
803
+
804
+ - HTTP request handling and error recovery
805
+ - Media state parsing and transformation
806
+ - Playlist management operations
807
+ - Volume control and mute functionality
808
+ - Album art URL generation and caching
809
+ - Mock external HTTP calls to Foobar2000 player during testing