Skip to content

Commit cd3e10f

Browse files
authored
Merge pull request #652 from openhab/update-eslint
Update eslint to 9.x / Add Copilot instructions
2 parents 45bda4e + ae4db35 commit cd3e10f

6 files changed

Lines changed: 578 additions & 286 deletions

File tree

.eslintrc.json

Lines changed: 0 additions & 47 deletions
This file was deleted.

.github/copilot-instructions.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# OpenHAB Google Assistant Copilot Instructions
2+
3+
## Repository Overview
4+
5+
This repository implements a Google Assistant Smart Home Action for OpenHAB, enabling voice control of IoT devices through Google Assistant. It's a Node.js serverless application using Google Cloud Functions that connects Google Assistant to OpenHAB instances via OpenHAB Cloud service.
6+
7+
**Architecture**: Node.js serverless application (Google Cloud Functions) with Express.js test server
8+
**Languages**: JavaScript (ES2020)
9+
**Framework**: Google Actions on Google SDK
10+
**Testing**: Jest with high coverage requirements (>96%)
11+
**Size**: Medium-sized project (~60 source files, extensive test coverage)
12+
13+
## Build & Development Process
14+
15+
### Prerequisites
16+
- Node.js 20.x (specified in CI/CD workflow)
17+
- npm 11.6.0+ (specified as packageManager)
18+
- Google Cloud SDK (for deployment)
19+
20+
### Essential Commands (Run in Order)
21+
22+
1. **Install Dependencies** (ALWAYS run first):
23+
```bash
24+
npm install
25+
cd functions && npm install && cd ..
26+
```
27+
28+
2. **Lint Code** (run before any commits):
29+
```bash
30+
npm run lint
31+
```
32+
33+
3. **Fix Linting Issues**:
34+
```bash
35+
npm run fix
36+
```
37+
38+
4. **Run Tests** (required for CI):
39+
```bash
40+
npm test
41+
# OR for CI environment:
42+
npm run test-ci
43+
```
44+
45+
5. **Start Development Server**:
46+
```bash
47+
npm start
48+
# Server listens on port 3000 (configurable via OH_SERVER_PORT)
49+
```
50+
51+
### Build Validation
52+
- **Tests**: Must pass with >96% coverage across all metrics (statements, branches, functions, lines)
53+
- **Linting**: Uses ESLint with Prettier, must pass with zero errors
54+
- **Time Requirements**: Tests complete in ~2-3 seconds, linting is fast
55+
56+
### Environment Configuration
57+
The application requires these environment variables:
58+
- `OH_HOST`: OpenHAB Cloud host (default: test.host in tests)
59+
- `OH_PORT`: Port (default: 443, test: 1234)
60+
- `OH_PATH`: REST API path (default: /rest/items/)
61+
62+
## Project Structure & Key Files
63+
64+
### Core Architecture
65+
- **Entry Point**: `functions/index.js` - Main Google Assistant handler
66+
- **Configuration**: `functions/config.js` - Backend endpoint configuration
67+
- **API Handler**: `functions/apihandler.js` - OpenHAB communication layer
68+
- **OpenHAB Logic**: `functions/openhab.js` - Core business logic
69+
70+
### Directory Layout
71+
```
72+
/functions/ # Google Cloud Function source
73+
/commands/ # Google Assistant command handlers (35+ files)
74+
/devices/ # Device type implementations (40+ files)
75+
index.js # Main entry point
76+
config.js # Configuration
77+
package.json # Cloud Function dependencies
78+
/tests/ # Comprehensive test suite
79+
/commands/ # Command handler tests
80+
/devices/ # Device tests
81+
setenv.js # Test environment setup
82+
testServer.js # Local development server
83+
```
84+
85+
### Configuration Files
86+
- `eslint.config.mjs`: ESLint 9.x flat config + Prettier (printWidth: 120, singleQuote: true)
87+
- `.markdownlint.yaml`: Markdown linting (MD013, MD025, MD033, MD040 disabled)
88+
- `package.json`: Root dependencies (Express for dev server)
89+
- `functions/package.json`: Cloud Function dependencies (actions-on-google)
90+
91+
## CI/CD Pipeline & Validation
92+
93+
### GitHub Workflows
94+
1. **Markdown Checks** (PRs only): Linting, spell check, grammar check
95+
2. **Unit Testing**: Node.js 20.x, install deps, lint, test with coverage
96+
3. **Code Analysis**: CodeQL security scanning
97+
4. **Deployment**: Auto-deploy to Google Cloud Functions on tags/releases
98+
99+
### Validation Steps
100+
1. `npm ci` (install dependencies)
101+
2. `npm run lint` (ESLint validation)
102+
3. `npm run test-ci` (Jest with coverage)
103+
4. Coverage upload to artifacts
104+
5. Google Cloud Functions deployment (nodejs20 runtime)
105+
106+
### Deployment Configuration
107+
- **Runtime**: nodejs20
108+
- **Entry Point**: openhabGoogleAssistant
109+
- **Region**: us-central1
110+
- **Memory**: 256MB
111+
- **Timeout**: 180s
112+
- **Instances**: 1-20 (min-max)
113+
114+
## Critical Development Notes
115+
116+
### Testing Requirements
117+
- **ALWAYS** run `npm install` in both root and `functions/` directories
118+
- Tests require >96% coverage across all metrics
119+
- Test environment uses mock OpenHAB host (test.host:1234)
120+
- Jest setup file: `tests/setenv.js` configures test environment
121+
122+
### Code Standards
123+
- **Line Length**: 120 characters max
124+
- **Style**: Prettier with single quotes, no trailing commas
125+
- **ES Version**: ES2020 with Node.js modules
126+
- **Error Handling**: Empty catch blocks allowed (allowEmptyCatch: true)
127+
- **ESLint**: v9.x with flat config format (`eslint.config.mjs`)
128+
- **Unused Variables**: Allowed in catch blocks (`caughtErrors: 'none'`)
129+
130+
### Deployment Gotchas
131+
- Two separate package.json files (root for dev, functions/ for runtime)
132+
- Google Cloud deployment uses only `functions/` directory
133+
- Environment variables injected via Cloud Functions configuration
134+
- Test vs production function names (openhabGoogleAssistant vs openhabGoogleAssistant_test)
135+
136+
### Common Workflow Issues
137+
- **Dependency Installation**: Must install in both root AND functions/ directories
138+
- **Coverage Failures**: Coverage thresholds are strict (>96%), failing tests will block CI
139+
- **Linting**: Prettier formatting is enforced, run `npm run fix` to auto-format
140+
- **Port Conflicts**: Dev server uses port 3000 by default (configurable)
141+
142+
## Writing Tests
143+
144+
### Test Structure & Patterns
145+
Tests use Jest framework with comprehensive mocking. Follow these established patterns:
146+
147+
**File Organization:**
148+
- Device tests: `tests/devices/[devicename].test.js`
149+
- Command tests: `tests/commands/[commandname].test.js`
150+
- Core logic tests: `tests/[module].test.js`
151+
152+
**Basic Test Structure:**
153+
```javascript
154+
const Device = require('../../functions/devices/[device].js');
155+
// or
156+
const Command = require('../../functions/commands/[command].js');
157+
158+
describe('[ComponentName]', () => {
159+
test('[functionality]', () => {
160+
expect(Component.method(params)).toBe(expectedResult);
161+
});
162+
});
163+
```
164+
165+
### Common Test Patterns
166+
167+
**Device Type Validation:**
168+
```javascript
169+
test('matchesDeviceType', () => {
170+
expect(Device.matchesDeviceType({
171+
type: 'Dimmer',
172+
metadata: { ga: { value: 'LIGHT' } }
173+
})).toBe(true);
174+
});
175+
```
176+
177+
**Command Parameter Validation:**
178+
```javascript
179+
test('validateParams', () => {
180+
expect(Command.validateParams({})).toBe(false);
181+
expect(Command.validateParams({ on: true })).toBe(true);
182+
});
183+
```
184+
185+
**HTTP Mocking with Nock:**
186+
```javascript
187+
const nock = require('nock');
188+
189+
afterEach(() => {
190+
nock.cleanAll();
191+
});
192+
193+
test('API call', async () => {
194+
const scope = nock('https://example.org')
195+
.get('/items/TestItem')
196+
.reply(200, { name: 'TestItem' });
197+
198+
const result = await apiHandler.getItem('TestItem');
199+
expect(result).toEqual({ name: 'TestItem' });
200+
});
201+
```
202+
203+
**Method Mocking with Jest:**
204+
```javascript
205+
beforeEach(() => {
206+
jest.spyOn(openHAB, 'handleSync').mockReset();
207+
});
208+
209+
test('method behavior', async () => {
210+
const mockFn = jest.spyOn(openHAB, 'handleSync');
211+
mockFn.mockResolvedValue({ devices: [] });
212+
213+
const result = await openHAB.onSync({ requestId: '1234' }, {});
214+
expect(mockFn).toHaveBeenCalledTimes(1);
215+
});
216+
```
217+
218+
### Test Environment Setup
219+
- Environment variables set in `tests/setenv.js` (auto-loaded by Jest)
220+
- Mock OpenHAB host: `test.host:1234`
221+
- Use `nock.cleanAll()` in `afterEach()` for HTTP mocks
222+
223+
## Trust These Instructions
224+
225+
These instructions are comprehensive and validated. Only search for additional information if:
226+
1. Commands fail with specific error messages not covered here
227+
2. New files or configurations are discovered that aren't documented
228+
3. Environment-specific issues arise that require investigation
229+
230+
The build process is well-established and stable. Follow the documented command sequence for reliable results.

.github/workflows/ci-cd.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ jobs:
3737
uses: reviewdog/action-languagetool@v1
3838
with:
3939
github_token: ${{ secrets.github_token }}
40+
patterns: '*.md docs/*.md'
4041

4142
unit-testing:
4243
name: Unit Testing

eslint.config.mjs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import js from '@eslint/js';
2+
import prettier from 'eslint-config-prettier';
3+
import prettierPlugin from 'eslint-plugin-prettier';
4+
5+
export default [
6+
js.configs.recommended,
7+
prettier,
8+
{
9+
files: ['**/*.js'],
10+
languageOptions: {
11+
ecmaVersion: 2020,
12+
sourceType: 'module',
13+
globals: {
14+
Atomics: 'readonly',
15+
SharedArrayBuffer: 'readonly',
16+
process: 'readonly',
17+
console: 'readonly',
18+
Buffer: 'readonly',
19+
__dirname: 'readonly',
20+
__filename: 'readonly',
21+
module: 'readonly',
22+
require: 'readonly',
23+
exports: 'readonly',
24+
global: 'readonly',
25+
setTimeout: 'readonly',
26+
clearTimeout: 'readonly',
27+
setInterval: 'readonly',
28+
clearInterval: 'readonly',
29+
setImmediate: 'readonly',
30+
clearImmediate: 'readonly'
31+
}
32+
},
33+
plugins: {
34+
prettier: prettierPlugin
35+
},
36+
rules: {
37+
'prettier/prettier': [
38+
'error',
39+
{
40+
singleQuote: true,
41+
trailingComma: 'none',
42+
tabWidth: 2,
43+
printWidth: 120
44+
}
45+
],
46+
'no-empty': [
47+
'error',
48+
{
49+
allowEmptyCatch: true
50+
}
51+
],
52+
'no-unused-vars': [
53+
'error',
54+
{
55+
caughtErrors: 'none'
56+
}
57+
],
58+
'max-len': [
59+
'error',
60+
{
61+
code: 120,
62+
tabWidth: 2,
63+
ignoreUrls: true
64+
}
65+
]
66+
}
67+
},
68+
{
69+
files: ['tests/**/*.js'],
70+
languageOptions: {
71+
globals: {
72+
describe: 'readonly',
73+
test: 'readonly',
74+
expect: 'readonly',
75+
beforeEach: 'readonly',
76+
afterEach: 'readonly',
77+
beforeAll: 'readonly',
78+
afterAll: 'readonly',
79+
jest: 'readonly'
80+
}
81+
}
82+
}
83+
];

0 commit comments

Comments
 (0)