|
| 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. |
0 commit comments