bimplus-websdk 1.0.63 → 1.0.65

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,64 @@
1
+ # GitHub Copilot Instructions — Bimplus WebSDK
2
+
3
+ > **Path convention:** This repository contains multiple packages. Unless stated otherwise, **all paths in this document are relative to the `websdk/` package root** (i.e. `websdk/` in the repository). For example, `src/Api/Api.js` refers to `websdk/src/Api/Api.js` in the repository, and `types/bimplus-websdk.d.ts` refers to `websdk/types/bimplus-websdk.d.ts`.
4
+
5
+ ## Project Overview
6
+ This is the **Bimplus WebSDK** (`bimplus-websdk`), a JavaScript wrapper around the Bimplus REST API developed by Allplan. It is a UMD library built with Webpack + Babel, targeting both browser and Node/CommonJS consumers. The entry point is `websdk/src/Api/Api.js` and the compiled output is `websdk/dist/bimplus-websdk.js`.
7
+
8
+ ## Repository Structure
9
+ All paths below are relative to `websdk/`:
10
+ - `src/Api/` — All API module source files (one class per resource, e.g. `Projects.js`, `Models.js`)
11
+ - `test/api_tests/` — QUnit integration tests (one file per API module, suffixed `Tests.js`)
12
+ - `test/helpers/` — Shared test helpers (`ApiHelper.js`, etc.)
13
+ - `types/bimplus-websdk.d.ts` — TypeScript type declarations for the public API
14
+ - `dist/` — Build output (do not edit manually)
15
+ - `documentation/` — Auto-generated docs (JSDoc via `documentation` package)
16
+
17
+ ## Language & Style
18
+ - **ES2022 modules** (`import`/`export`) throughout `src/` and `test/`
19
+ - **No TypeScript** in source files — only `.js`. The `types/` folder contains handwritten `.d.ts` declarations that must be kept in sync manually when adding new public methods.
20
+ - **Semicolons are required** (enforced by ESLint: `semi: ["warn", "always"]`).
21
+ - Use `const`/`let` — never `var`.
22
+ - Module pattern: each API resource is a constructor function (not a class), attached to the `Api` class instance via `this.<namespace> = new ResourceName(this)`.
23
+ - All async API calls return a **jQuery `$.ajax` promise** (via `AjaxRequest` / `RequestUtils.js`). Do not use `fetch` or `axios` in `src/`.
24
+ - Keep JSDoc comments on every public method following the existing `@namespace` / `@memberof` / `@param` / `@return` pattern so `documentation build` continues to work.
25
+
26
+ ## Adding a New API Module
27
+ 1. Create `src/Api/MyResource.js` following the existing constructor-function pattern.
28
+ 2. Import and instantiate it in `src/Api/Api.js` (follow the existing import list and `this.myResource = new MyResource(this)` in the constructor).
29
+ 3. Add corresponding tests in `test/api_tests/MyResourceTests.js` using QUnit + `ApiHelper`.
30
+ 4. Add TypeScript declarations in `types/bimplus-websdk.d.ts`.
31
+ 5. Document with JSDoc `@namespace`/`@memberof api` tags.
32
+
33
+ ## Testing
34
+ - Framework: **QUnit** running inside **Karma** (browser-based).
35
+ - Tests are integration tests that call the real Bimplus dev API — they require `BIM_PASS` env variable.
36
+ - Run all tests: `npm test`
37
+ - Run headless Chrome: `npm run test-chrome-headless`
38
+ - Test helpers live in `test/helpers/ApiHelper.js`. Use `ApiHelper` to set up team/project context before assertions.
39
+ - Assert HTTP error responses with `assertHttpStatusText(assert, e, "Bad Request")` (global helper injected by Karma).
40
+
41
+ ## Build
42
+ - Dev build (with source maps): `npm run build`
43
+ - Production build (minified): `npm run build-prod`
44
+ - Lint: `npm run eslint`
45
+ - Output format: UMD, entry `src/Api/Api.js` → `dist/bimplus-websdk.js`
46
+ - Webpack config split: `webpack.common.js` (shared), `webpack.dev.js`, `webpack.prod.js`
47
+
48
+ ## Documentation
49
+ - Generate HTML docs: `npm run build-doc`
50
+ - Generate Markdown: `npm run build-docMd`
51
+ - Every public method in `src/Api/*.js` must pass `documentation lint` (`npm run lint-doc`) — keep JSDoc valid.
52
+
53
+ ## Key Conventions
54
+ - URL helpers: use `api.getApiTeamUrl()` for team-scoped endpoints, `api.getApiUrl()` for global endpoints.
55
+ - Query parameters are passed as `{ queryParams: { key: value } }` in the `params` argument to `api.request.get()`.
56
+ - Chunked upload/download helpers (`getChunked`, `putChunked`) are in `AjaxRequest.js` — use them for large payloads.
57
+ - IDB caching is opt-in via `IDBCache.js` / `CachedRequest.js`; do not add caching logic directly to resource modules.
58
+ - Global flags `window.BIMPLUS_SDK_ENABLE_CACHE`, `window.BIMPLUS_SDK_LOG_API_CALLS`, `window.BIMPLUS_SDK_DISABLE_ETAG`, `window.BIMPLUS_SDK_PENDING_REQUESTS` are intentional public hooks — do not remove them.
59
+
60
+ ## What to Avoid
61
+ - Do not introduce new runtime dependencies without discussing it first; the current dependency footprint (`async`, `dexie`, `jquery`) is intentionally minimal.
62
+ - Do not convert constructor functions to ES6 `class` syntax — this would break backward compatibility with consumers using the UMD bundle.
63
+ - Do not add `async/await` to `src/` code without ensuring Babel transpilation handles it (check `@babel/plugin-transform-runtime` config).
64
+ - Do not commit credentials, tokens, or real GUIDs from the test environment into source files.
@@ -0,0 +1,144 @@
1
+ ---
2
+ applyTo: "websdk/src/Api/Api.js, websdk/src/Api/AjaxRequest.js, websdk/src/Api/RequestUtils.js"
3
+ ---
4
+
5
+ # Bimplus WebSDK — Core API & Request Layer
6
+
7
+ ## Api Class Initialization
8
+
9
+ ```js
10
+ import { Api, createDefaultConfig } from './src/Api/Api';
11
+
12
+ const config = createDefaultConfig('dev'); // 'dev' | 'stage' | 'prod' (default)
13
+ const api = new Api(config);
14
+ ```
15
+
16
+ ### `createDefaultConfig(env?)` Environments
17
+
18
+ | `env` value | Host |
19
+ |---|---|
20
+ | `'dev'` | `api-dev.bimplus.net` |
21
+ | `'stage'` | `api-stage.bimplus.net` |
22
+ | `'prod'` / omitted | `api.bimplus.net` |
23
+
24
+ ### Config Shape
25
+
26
+ ```json
27
+ {
28
+ "protocol": "https://",
29
+ "host": "api-dev.bimplus.net",
30
+ "version": "v2",
31
+ "cache": false
32
+ }
33
+ ```
34
+
35
+ - `version` defaults to `'v2'` if omitted from a custom config object.
36
+ - `cache: true` enables IDB-backed caching via `CachedRequest`. If IndexedDB is not supported by the browser, it falls back silently to `AjaxRequest` with a console warning.
37
+
38
+ ---
39
+
40
+ ## Authentication & Setup Flow
41
+
42
+ After constructing `Api`, always perform these three steps before calling any resource API:
43
+
44
+ ```js
45
+ // 1. Obtain a token
46
+ const auth = await api.authorize.post(username, password, applicationId);
47
+
48
+ // 2. Set the token (token type 'BimPlus' is the standard)
49
+ api.setAccessTokenAndType(auth.access_token, 'BimPlus');
50
+
51
+ // 3. Set the team context (required for all team-scoped endpoints)
52
+ api.setTeamSlug('my-team-slug');
53
+ ```
54
+
55
+ | Method | Purpose |
56
+ |---|---|
57
+ | `api.setAccessToken(token)` | Sets token only |
58
+ | `api.setTokenType(type)` | Sets token type only |
59
+ | `api.setAccessTokenAndType(token, type)` | Sets both at once (preferred) |
60
+ | `api.setTeamSlug(slug)` | Required before any team-scoped request |
61
+ | `api.getTeamSlug()` | Returns current team slug |
62
+ | `api.getAccessToken()` | Returns current token |
63
+ | `api.getAccessTokenAndType()` | Returns `{ token, tokenType }` |
64
+
65
+ ---
66
+
67
+ ## URL Building
68
+
69
+ | Method | Example output |
70
+ |---|---|
71
+ | `api.getApiUrl()` | `https://api-dev.bimplus.net/v2/` |
72
+ | `api.getApiTeamUrl()` | `https://api-dev.bimplus.net/v2/my-team-slug/` |
73
+
74
+ - `getApiTeamUrl()` logs a `console.error` if `teamSlug` is not set — it does **not** throw.
75
+ - Use `getApiUrl()` for global (non-team-scoped) endpoints: `authorize`, `auth-forgot`, `cross-token`, `validate`, `administration/backups`, `.well-known/openid-configuration`.
76
+ - Use `getApiTeamUrl()` for all other endpoints (projects, objects, divisions, issues, etc.).
77
+
78
+ ---
79
+
80
+ ## Request Layer (`AjaxRequest`)
81
+
82
+ All resource modules call `api.request.get / put / post / del`. The signatures are:
83
+
84
+ ```js
85
+ api.request.get(url, ajaxParams?)
86
+ api.request.put(url, data, ajaxParams?)
87
+ api.request.post(url, data, ajaxParams?)
88
+ api.request.del(url, data?, ajaxParams?)
89
+ ```
90
+
91
+ ### `ajaxParams` shape
92
+
93
+ ```js
94
+ {
95
+ queryParams: { key: 'value' }, // appended as URL query string
96
+ contentType: 'application/json; charset=utf-8', // default
97
+ dataType: 'json', // default; set to null for non-JSON responses
98
+ processData: false, // default
99
+ eTag: '<etag-value>', // adds If-None-Match header (unless BIMPLUS_SDK_DISABLE_ETAG is set)
100
+ headers: { 'X-Custom': 'val' }, // extra request headers
101
+ useXMLHttpRequest: true, // use XMLHttpRequest instead of $.ajax (e.g. binary/streaming)
102
+ responseType: 'arraybuffer', // XHR binary response type
103
+ xhrUploadProgress: function(e) {}, // upload progress callback for XHR
104
+ }
105
+ ```
106
+
107
+ ### Chunked helpers
108
+
109
+ Use these for large payloads — never implement custom chunking inside resource modules:
110
+
111
+ ```js
112
+ api.request.getChunked(url, ajaxConfig) // chunked GET
113
+ api.request.putChunked(url, data, ajaxConfig) // chunked PUT
114
+ ```
115
+
116
+ ---
117
+
118
+ ## Global Window Flags
119
+
120
+ All flags are optional. They must be set **before** any request is made.
121
+
122
+ | Flag | Type | Effect |
123
+ |---|---|---|
124
+ | `window.BIMPLUS_SDK_ENABLE_CACHE` | `boolean` | Enables jQuery `$.ajax` browser-level cache |
125
+ | `window.BIMPLUS_SDK_LOG_API_CALLS` | `boolean` | Console-logs every request URL + parsed response |
126
+ | `window.BIMPLUS_SDK_DISABLE_ETAG` | `boolean` | Suppresses `If-None-Match` header even when `eTag` is set in ajaxParams |
127
+ | `window.BIMPLUS_SDK_PENDING_REQUESTS` | `number` | Incremented on request start, decremented on complete — useful to track in-flight requests |
128
+ | `window.BIMPLUS_SDK_ENABLE_DEBUG_LOGS` | `boolean` | Logs token, tokenType, teamSlug, and config to console during `Api` initialization |
129
+
130
+ > `BIMPLUS_SDK_ENABLE_DEBUG_LOGS` is intentional — do not remove it.
131
+
132
+ ---
133
+
134
+ ## Models vs. Divisions Naming
135
+
136
+ The Bimplus REST API uses the term **"divisions"** for what the SDK exposes as **`api.models`**. This is a legacy naming mismatch:
137
+
138
+ | SDK namespace | REST endpoint |
139
+ |---|---|
140
+ | `api.models.get(modelId)` | `GET .../divisions/:id` |
141
+ | `api.models.getRevisions(divisionId)` | `GET .../divisions/:id/revisions` |
142
+ | `api.models.getDisciplines(divisionId)` | `GET .../divisions/:id/disciplines` |
143
+
144
+ Always use `api.models` in SDK code. Use `divisionId` as the parameter name for the ID (not `modelId`) when the underlying endpoint says `divisions`.
@@ -0,0 +1,172 @@
1
+ ---
2
+ applyTo: "websdk/src/Api/Projects.js, websdk/test/api_tests/Project*.js, websdk/types/bimplus-websdk.d.ts"
3
+ ---
4
+
5
+ # Bimplus Project Attributes
6
+
7
+ ## Core Project Object (`projects.get()`)
8
+
9
+ When retrieving a project via `api.projects.get(id?)`, the returned JSON object contains these fields:
10
+
11
+ ```json
12
+ {
13
+ "id": "<guid>",
14
+ "name": "My Project",
15
+ "shortDescr": "Short project description",
16
+ "address": "Street, City, Country",
17
+ "teamSlug": "my-team-slug",
18
+ "teamName": "My Team",
19
+ "rights": { ... },
20
+ "disciplines": [ ... ]
21
+ }
22
+ ```
23
+
24
+ | Field | Type | Notes |
25
+ |---|---|---|
26
+ | `id` | `string (Guid)` | Unique project identifier |
27
+ | `name` | `string` | Display name of the project |
28
+ | `shortDescr` | `string` | Short description / subtitle |
29
+ | `address` | `string` | Physical address of the project |
30
+ | `teamSlug` | `string` | URL slug of the owning team |
31
+ | `teamName` | `string` | Display name of the owning team |
32
+ | `rights` | `object` | Current user's access rights on this project |
33
+ | `disciplines` | `array` | List of model disciplines; omitted when `noDisciplines=true` |
34
+
35
+ > `disciplines` can be suppressed by calling `api.projects.get(id, true)` for performance on large project lists.
36
+
37
+ ---
38
+
39
+ ## Extended Attribute Object (`objects.getAttributes(projectId, "", projectId, "")`)
40
+
41
+ The full project attribute set — as shown on the BimPortal project detail page — is retrieved via:
42
+
43
+ ```js
44
+ const projectAttributes = await api.objects.getAttributes(projectId, "", projectId, "");
45
+ ```
46
+
47
+ ### Top-level shape
48
+
49
+ ```json
50
+ {
51
+ "id": "<project guid>",
52
+ "name": "My Project",
53
+ "type": "Project",
54
+ "elementtyp": "8d27ae6d-3c9a-4201-8a4d-bf0225861788",
55
+ "attributes": { ... },
56
+ "localizedAttributeGroups": { ... }
57
+ }
58
+ ```
59
+
60
+ | Field | Value / Notes |
61
+ |---|---|
62
+ | `id` | Project GUID |
63
+ | `name` | Project name |
64
+ | `type` | Always `"Project"` |
65
+ | `elementtyp` | Fixed GUID `8d27ae6d-3c9a-4201-8a4d-bf0225861788` — identifies the project element type in Bimplus |
66
+ | `attributes` | Map of attribute groups (see below) |
67
+ | `localizedAttributeGroups` | Localized display names for each group key |
68
+
69
+ ### Attribute groups (`attributes`)
70
+
71
+ The `attributes` object always contains exactly **7 groups**:
72
+
73
+ | Group key | Description |
74
+ |---|---|
75
+ | `address` | Physical address fields of the project |
76
+ | `others` | Miscellaneous project settings (see details below) |
77
+ | `project` | Core project metadata |
78
+ | `project contact` | Contact person information |
79
+ | `project meeting` | Project meeting data |
80
+ | `project offset` | Internal coordinate system offset (x, y, z) |
81
+ | `project statistics` | Computed statistics and counters |
82
+
83
+ ### Individual attribute shape
84
+
85
+ Each entry inside a group is keyed by a localized attribute name and has the following structure:
86
+
87
+ ```json
88
+ {
89
+ "id": "<guid>",
90
+ "name": "Offset X",
91
+ "description": "Offset X",
92
+ "group": "project offset",
93
+ "type": "double",
94
+ "unit": "m",
95
+ "value": 100
96
+ }
97
+ ```
98
+
99
+ | Field | Notes |
100
+ |---|---|
101
+ | `id` | Stable GUID — use this for lookups, not the name (names can be localized) |
102
+ | `name` | Human-readable attribute name |
103
+ | `description` | Longer description of the attribute |
104
+ | `group` | The group this attribute belongs to |
105
+ | `type` | Data type: `"string"`, `"double"`, `"boolean"`, `"guid"`, etc. |
106
+ | `unit` | Unit string, e.g. `"m"` — may be absent for non-numeric types |
107
+ | `value` | The current value |
108
+
109
+ ---
110
+
111
+ ## Well-known Attribute GUIDs
112
+
113
+ Use `id` for reliable lookups — never match by `name` alone (names are localizable).
114
+
115
+ ### `project offset` group — internal offsets
116
+
117
+ | Attribute | GUID | Type | Unit |
118
+ |---|---|---|---|
119
+ | Offset X | `e276009f-f637-43ef-90ee-8e145e8dc41c` | `double` | `m` |
120
+ | Offset Y | `2e112c8a-47fb-40ba-b7b3-3f601daa127c` | `double` | `m` |
121
+ | Offset Z | `d0cbf9eb-25b2-4443-b23d-8c67396822e8` | `double` | `m` |
122
+
123
+ ### `others` group — user-defined global offsets and settings
124
+
125
+ | Attribute | GUID | Type | Unit |
126
+ |---|---|---|---|
127
+ | Δ x (global coordinate x) | `ecea05de-9450-48f8-a8f2-f1de32b7cd5c` | `double` | `m` |
128
+ | Δ y (global coordinate y) | `6d0e5735-2c05-4b24-90c2-663ba234a6f8` | `double` | `m` |
129
+ | Δ z (global coordinate z) | `43b6bb07-0b1f-49ff-b226-33befba937e2` | `double` | `m` |
130
+ | Default language for BCF export | `2ae6edb6-bfbd-4ce6-ae7e-7dcd7b0abc2e` | `string` | — |
131
+ | Project currency | `513d2bdd-f994-46b6-a629-0a5e694893a5` | `string` | — |
132
+ | Properties template | `3d7404a8-f2e7-4418-b87a-daf1597cfb49` | `guid` | — |
133
+ | RestrictMemberAccessInPortal | `f4437afc-c55b-4227-9d97-89035ce8dafd` | `boolean` | — |
134
+ | Units (Units and Dimensions) | `a907b2b1-91f2-46e6-a045-7fee485c458b` | `guid` | — |
135
+
136
+ ---
137
+
138
+ ## Related Sub-resources (via `api.projects.*`)
139
+
140
+ The following child resources are accessible directly from a project ID:
141
+
142
+ | Method | Endpoint pattern | Returns |
143
+ |---|---|---|
144
+ | `getModels(projectId)` | `.../projects/:id/divisions` | Array of model/discipline objects |
145
+ | `getMembers(projectId)` | `.../projects/:id/members` | Array of `{ member, group }` objects |
146
+ | `getPins(projectId)` | `.../projects/:id/pins` | Array of pin objects |
147
+ | `getSpots(projectId)` | `.../projects/:id/pins` | Alias for pins |
148
+ | `getAttachments(projectId, revision?, queryParams?)` | `.../projects/:id/attachments` | Array of attachment objects |
149
+ | `getTopology(projectId)` | `.../projects/:id/topology` | Topology tree with `children` |
150
+ | `getComments(projectId)` | `.../projects/:id/comments` | Array of comment objects |
151
+ | `getHyperlinks(projectId)` | `.../projects/:id/hyperlinks` | Array of hyperlink objects |
152
+ | `getIssues(projectId, filterScene?, thumbnailUrl?)` | `.../projects/:id/issues` | Array of issue/task objects |
153
+ | `getIssuesShortInfo(projectId)` | `.../projects/:id/issues?shortinfo=true` | Array of summarized issue info |
154
+ | `getSlideshows(projectId)` | `.../projects/:id/slideshows` | Array of slideshow objects |
155
+ | `getThumbnail(thumbnailId)` | `.../thumbnail/:id` | Thumbnail data |
156
+ | `getProjectInfo(projectId)` | `.../projects/:id` | Same shape as `projects.get(id)` |
157
+
158
+ ---
159
+
160
+ ## Writable Project Fields (POST / PUT)
161
+
162
+ When creating (`projects.post(data)`) or updating (`projects.put(id, data)`) a project, pass a JSON-stringified object with:
163
+
164
+ ```json
165
+ {
166
+ "name": "My Awesome Building",
167
+ "shortDescr": "Example description",
168
+ "address": "Street, City"
169
+ }
170
+ ```
171
+
172
+ The response returns the full project object including the server-assigned `id` and `teamSlug`.
@@ -0,0 +1,209 @@
1
+ ---
2
+ applyTo: "websdk/test/api_tests/**, websdk/test/helpers/**"
3
+ ---
4
+
5
+ # Bimplus WebSDK — Test Conventions
6
+
7
+ ## Test Framework
8
+
9
+ - **QUnit** + **Karma** (browser-based integration tests against the real Bimplus API)
10
+ - Tests are **not** unit tests — they require a live API connection and valid credentials
11
+ - One test file per API module, named `<Module>Tests.js` in `test/api_tests/`
12
+
13
+ ---
14
+
15
+ ## ApiHelper Setup
16
+
17
+ Import `ApiHelper` at the top of every test file:
18
+
19
+ ```js
20
+ import { ApiHelper } from '../helpers/ApiHelper';
21
+ ```
22
+
23
+ Instantiate it inside each `QUnit.test` callback (not at module level):
24
+
25
+ ```js
26
+ const helper = new ApiHelper();
27
+ ```
28
+
29
+ ### Team and Project Context
30
+
31
+ Always set context before calling resource APIs:
32
+
33
+ ```js
34
+ await helper.setTeamByName(); // uses helper.defaultTeam
35
+ await helper.setTeamByName('My Team'); // specific team by display name
36
+
37
+ const projectId = await helper.setProjectByName(); // uses helper.BasicProject.name
38
+ const projectId = await helper.setProjectByName(null, 'Proj'); // specific project name
39
+ ```
40
+
41
+ - `setTeamByName` performs `login()` automatically, looks up the team by name, and calls `api.setTeamSlug()`.
42
+ - `setProjectByName` also calls `setTeamByName` internally — no need to call both.
43
+ - Both return the resolved `id`.
44
+
45
+ ### Accessing the API Instance
46
+
47
+ ```js
48
+ helper.api.projects.get(projectId);
49
+ helper.api.models.get(modelId);
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Environment Variables
55
+
56
+ | Variable | Environment | Purpose |
57
+ |---|---|---|
58
+ | `BIM_PASS` | `dev` | Password for `bimplus.web.sdk@allplan.com` on dev |
59
+ | `BIM_PASS_STAGE` | `stage` | Password for the same user on stage |
60
+ | `BIM_PASS_PROD` | `prod` | Password for the same user on prod |
61
+
62
+ The environment is auto-detected by `ApiHelper` from the Karma `host` config. Never hard-code passwords.
63
+
64
+ ---
65
+
66
+ ## Test Fixture Constants
67
+
68
+ All GUIDs for test data are exposed on the `ApiHelper` instance. Always reference these — never hard-code GUIDs in test files.
69
+
70
+ ### Shared fixtures (environment-independent)
71
+
72
+ | Property | Description |
73
+ |---|---|
74
+ | `helper.BasicProject` | Main project for most object/topology/filter tests |
75
+ | `helper.BasicProject.projectId` | Project GUID |
76
+ | `helper.BasicProject.divisionId_m1` | Division (model) GUID for model `m1` |
77
+ | `helper.BasicProject.wallId_m1` | Object GUID for a wall in model `m1` |
78
+ | `helper.BasicProject.doorId_m1` | Object GUID for a door in model `m1` |
79
+ | `helper.BasicProjectTest` | Project used for create/update/delete project tests |
80
+ | `helper.BasicProjectWithOffsets` | Project with known coordinate offsets |
81
+ | `helper.BasicStructuresTest` | Project for structure tests |
82
+ | `helper.BasicMarkupTests` | Project for annotation/markup tests |
83
+ | `helper.BasicSlideshowTests` | Project for slideshow tests |
84
+ | `helper.undefinedId` | `"00000000-0000-0000-0000-000000000000"` — use for "not found" tests |
85
+ | `helper.dummyId` | `"AAAAAAAA-0000-AAAA-0000-000000000000"` — use for invalid-ID tests |
86
+
87
+ ### Environment-specific fixtures (differ between dev / stage / prod)
88
+
89
+ | Property | Description |
90
+ |---|---|
91
+ | `helper.BasicModelTest` | Project with 3 revisions (rev1Id, rev2Id, rev3Id) |
92
+ | `helper.BasicImportTest` | Project + model for IFC import tests |
93
+ | `helper.BasicOverlayTests` | Project with overlays and versioned attachments |
94
+ | `helper.BasicConnexisTest` | Project for structural Connexis tests |
95
+ | `helper.BasicWorkflowTest` | Project for workflow template tests |
96
+
97
+ ### User constants
98
+
99
+ | Property | Value |
100
+ |---|---|
101
+ | `helper.defaultEMail` | `bimplus.web.sdk@allplan.com` |
102
+ | `helper.defaultTeam` | Team display name for the current environment |
103
+ | `helper.defaultTeamSlug` | URL slug of the test team |
104
+ | `helper.defaultTeamId` | GUID of the test team |
105
+ | `helper.defaultAppId` | `5F43560D-9B0C-4F3C-85CB-B5721D098F7B` (shared across envs) |
106
+ | `helper.invitationUserEmail` | `bimplus.renderer.test@allplan.com` — for member/message tests |
107
+
108
+ ---
109
+
110
+ ## QUnit Test Pattern
111
+
112
+ ```js
113
+ QUnit.module("api_tests/MyResourceTests", function () {
114
+
115
+ QUnit.test("Get resource", async function (assert) {
116
+ assert.expect(2); // always declare expected assertion count up front
117
+
118
+ const helper = new ApiHelper();
119
+ await helper.setTeamByName();
120
+
121
+ const result = await helper.api.myResource.get();
122
+ assert.ok(result.length > 0);
123
+ assert.ok(result[0].name);
124
+ });
125
+
126
+ QUnit.test("Handle HTTP error", async function (assert) {
127
+ assert.expect(2);
128
+
129
+ const helper = new ApiHelper();
130
+ await helper.setTeamByName();
131
+
132
+ try {
133
+ await helper.api.myResource.get(helper.undefinedId);
134
+ assert.ok(false, 'Should have thrown');
135
+ } catch (e) {
136
+ assertHttpStatusText(assert, e, "Not Found"); // global injected by Karma — do NOT import it
137
+ }
138
+ });
139
+
140
+ });
141
+ ```
142
+
143
+ Key rules:
144
+ - **Always** call `assert.expect(N)` at the start of each test
145
+ - Use `async function` + `await` — no `.then()` chains or callbacks in test body
146
+ - Module name must match the file path exactly: `"api_tests/MyResourceTests"`
147
+ - `assertHttpStatusText` is a **Karma-injected global** — never import it
148
+ - Use `helper.undefinedId` or `helper.dummyId` to trigger expected HTTP errors, not random strings
149
+
150
+ ---
151
+
152
+ ## Specialist Test Helpers
153
+
154
+ For complex resource domains, additional helpers exist in `test/helpers/`:
155
+
156
+ | Helper | Used for |
157
+ |---|---|
158
+ | `AnnotationsHelper` | Creating annotation payloads |
159
+ | `LabelsHelper` | Creating label payloads with object IDs |
160
+ | `DimensionsHelper` | Creating dimension payloads with object IDs |
161
+ | `MultipartFormDataHelper` | Building multipart/form-data for file uploads |
162
+ | `MessagesHelper` | Cleaning up all messages for a user before tests run |
163
+
164
+ Import them alongside `ApiHelper` when needed:
165
+
166
+ ```js
167
+ import { LabelsHelper } from '../helpers/LabelsHelper';
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Message Cleanup in `beforeEach`
173
+
174
+ Some backend services (notably `ExportWorkerService`) deliver notification messages to `defaultUserId` **asynchronously** after an operation completes (e.g. an IFC export triggered by `ExportServiceTests`). Because test files run alphabetically and `ExportServiceTests` (E) runs well before `InvitationsTests` (I) and `MessagesTests` (M), these delayed notifications can arrive in the user's inbox during a later test, causing false failures on empty-inbox assertions.
175
+
176
+ **Any test module that asserts on message counts must call `deleteAllMessages` in its `beforeEach`:**
177
+
178
+ ```js
179
+ import { ApiHelper } from '../helpers/ApiHelper';
180
+ import { deleteAllMessages } from '../helpers/MessagesHelper';
181
+
182
+ QUnit.module("api_tests/MyTests", function (hooks) {
183
+
184
+ hooks.beforeEach(async function () {
185
+ const helper = new ApiHelper();
186
+ await helper.setProjectByName();
187
+ await deleteAllMessages(helper.api, helper.defaultUserId);
188
+ });
189
+
190
+ });
191
+ ```
192
+
193
+ `deleteAllMessages(api, userId)` fetches all four message endpoints in parallel — `getUserMessages` (inbox), `getSentUserMessages` (sent), `getSentGroupMessages` (group-sent), and `getUserDashboardMessages` (dashboard) — deduplicates IDs across all four, and deletes everything found in parallel. It exits only after **two consecutive empty checks**, confirming a stable empty state. This prevents the race where a background-service message arrives in the window between an empty fetch and the next test assertion. A 1-second pause separates each pass (up to 5 attempts total).
194
+
195
+ > **Do not** replicate this loop inline. Always use `deleteAllMessages` from `MessagesHelper`.
196
+
197
+ ### When to find messages by ID, not by index
198
+
199
+ When verifying that a newly created message appears in a list alongside possible system messages, always locate it by its known ID rather than assuming array position:
200
+
201
+ ```js
202
+ // Wrong — breaks when system messages sort before the test message
203
+ assert.equal(messages[0].topic, "MY_TOPIC");
204
+
205
+ // Correct
206
+ const testMessage = messages.find((m) => m.id === createdMessage.id);
207
+ assert.ok(testMessage, "test message found in sent messages");
208
+ assert.equal(testMessage.topic, "MY_TOPIC");
209
+ ```