@xenon-device-management/xenon 2.13.1 → 2.14.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/README.md +11 -5
- package/lib/package.json +1 -1
- package/lib/public/assets/{AnnotationOverlay-Cvpkk4r9.js → AnnotationOverlay-B_VNtKBH.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-Bhn5ZtVM.js → ApiKeyGate-BwuaMjkX.js} +1 -1
- package/lib/public/assets/{BugReportButton-BA6aEjTC.js → BugReportButton-BAt0_pzq.js} +1 -1
- package/lib/public/assets/DeviceMosaicView-BYaQHhTf.js +2 -0
- package/lib/public/assets/{EmptyState-CgzbuUvO.js → EmptyState-BSjhk_IV.js} +1 -1
- package/lib/public/assets/{FieldGroup-BHc_5qNc.js → FieldGroup-Cb5QZsE1.js} +1 -1
- package/lib/public/assets/{FilterMenu-D7OMGuRc.js → FilterMenu-BAaSlWbW.js} +1 -1
- package/lib/public/assets/{Menu-C173JJ0l.js → Menu-JGGYrSQQ.js} +1 -1
- package/lib/public/assets/{Modal-ChvVUI3B.js → Modal-CFCp3n9d.js} +1 -1
- package/lib/public/assets/{RecordingPage-Dhw4d_O6.js → RecordingPage-CbJj_EBT.js} +1 -1
- package/lib/public/assets/{RecordingsPage-z97ftGK-.js → RecordingsPage-BJeYnygR.js} +2 -2
- package/lib/public/assets/{SegmentedControl-DbXhBGZC.js → SegmentedControl-Bg42Atjy.js} +1 -1
- package/lib/public/assets/{SettingCard-C908GgiI.js → SettingCard-Njz1pUIJ.js} +1 -1
- package/lib/public/assets/{Table-B1DB_ZN-.js → Table-BcTm5xV2.js} +1 -1
- package/lib/public/assets/{activity-Css0xNvp.js → activity-DOVdsfMQ.js} +1 -1
- package/lib/public/assets/{ai-settings-BMQK3-yN.js → ai-settings-CsuJroRf.js} +2 -2
- package/lib/public/assets/{api-keys-CbJ42eDQ.js → api-keys-BKnNSTw9.js} +1 -1
- package/lib/public/assets/{apps-DW1pw2re.js → apps-D0SC6uw6.js} +2 -2
- package/lib/public/assets/{arrow-left-Dpog9L0F.js → arrow-left-MMbTmgoE.js} +1 -1
- package/lib/public/assets/{arrow-right-CsBIlSIw.js → arrow-right-BMfnGmxW.js} +1 -1
- package/lib/public/assets/{arrow-up-right-z1tRQE4R.js → arrow-up-right-DuAftMWB.js} +1 -1
- package/lib/public/assets/{auth-shell-mXiX5vNk.js → auth-shell-nZC2JNDO.js} +2 -2
- package/lib/public/assets/{builds-page-BcFn23bE.js → builds-page-hDCgTwkS.js} +1 -1
- package/lib/public/assets/{button-Cq_zHETv.js → button-Dda4irn_.js} +1 -1
- package/lib/public/assets/{calendar-CKxABX9q.js → calendar-BMr0Q62v.js} +1 -1
- package/lib/public/assets/{check-C7xu-hGU.js → check-CZ9iw9-e.js} +1 -1
- package/lib/public/assets/{chevron-right-BiqfdCGA.js → chevron-right-BRYJJVC9.js} +1 -1
- package/lib/public/assets/{circle-check-CyBjjPxR.js → circle-check-CqAJjQ5O.js} +1 -1
- package/lib/public/assets/{circle-x-6gMRcx6z.js → circle-x-DlDJtnVz.js} +1 -1
- package/lib/public/assets/{clock-U1rDTT1h.js → clock-CpuDKysU.js} +1 -1
- package/lib/public/assets/{copy-BIM0ZX0L.js → copy-CAQydNFe.js} +1 -1
- package/lib/public/assets/device-explorer-CdO6_nrn.js +282 -0
- package/lib/public/assets/{download-DDBRXnAZ.js → download-C9XG6lLv.js} +1 -1
- package/lib/public/assets/{forgot-password-Dpg7yxM8.js → forgot-password-CGnkRgE9.js} +1 -1
- package/lib/public/assets/h264Stream-VQBphYhg.js +26 -0
- package/lib/public/assets/{index-BGBqohfQ.js → index-BbKEXUFH.js} +5 -5
- package/lib/public/assets/{index-BTYMAqNW.css → index-Bxoxt9sV.css} +1 -1
- package/lib/public/assets/{index-BQ2luT7L.js → index-DJGc1M2f.js} +1 -1
- package/lib/public/assets/{input-BvB1cjHE.js → input-Cm0tmSoc.js} +1 -1
- package/lib/public/assets/{line-chart-m78VggnW.js → line-chart-D2DyJCml.js} +1 -1
- package/lib/public/assets/{list-checks-cY7o1QTL.js → list-checks-UWf1GY99.js} +1 -1
- package/lib/public/assets/{lock-loz8txQm.js → lock-CCJv9m_I.js} +1 -1
- package/lib/public/assets/{login-BYOvz4lx.js → login-f8vOVW-U.js} +1 -1
- package/lib/public/assets/maintenance-settings-C1PrkuAH.js +6 -0
- package/lib/public/assets/{monitor-C4HPHb6s.js → monitor-B2jDgwm7.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-BkdKtCzi.js → mouse-pointer-2-DLI1RtzU.js} +1 -1
- package/lib/public/assets/{network-C5e0MIWo.js → network-ebT86qoi.js} +1 -1
- package/lib/public/assets/{overview-tlu8ijGd.js → overview-C0aykVrd.js} +1 -1
- package/lib/public/assets/{page-header-FXPlo6az.js → page-header-BjzgWz-T.js} +1 -1
- package/lib/public/assets/{play-CWdk0_Sy.js → play-DLG9t8FU.js} +1 -1
- package/lib/public/assets/{plus-B2xCnntV.js → plus-nBFSMu_X.js} +1 -1
- package/lib/public/assets/{profile-page-D080qzr4.js → profile-page-CSl0UYlv.js} +1 -1
- package/lib/public/assets/{reset-password-D3u4Mbvn.js → reset-password-LeOuHqmB.js} +1 -1
- package/lib/public/assets/{runbook-page-hPRZMy7s.js → runbook-page-CMMeQml9.js} +1 -1
- package/lib/public/assets/{select-12RThAXc.js → select-88jHcl-Z.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-uQwvV7EQ.js → selector-detail-redirect-BbXKJjZM.js} +1 -1
- package/lib/public/assets/{selector-health-page-CThoxDFO.js → selector-health-page-B4rA1Xkv.js} +2 -2
- package/lib/public/assets/session-detail-page-COL_RZxw.js +4 -0
- package/lib/public/assets/settings-GF6ZMSQ6.js +6 -0
- package/lib/public/assets/{stat-tile-B02WEte8.js → stat-tile-BV98e_Sv.js} +1 -1
- package/lib/public/assets/{tablet-Dj30VGZl.js → tablet-BiAsnFDj.js} +1 -1
- package/lib/public/assets/{teams-uL4fjTDR.js → teams-B4-jrr-1.js} +1 -1
- package/lib/public/assets/{trash-2-B54e-TfY.js → trash-2-C5RSuX0e.js} +1 -1
- package/lib/public/assets/{upload-B3k8WCSP.js → upload-BG7_rMXK.js} +1 -1
- package/lib/public/assets/{use-builds-data-BFn90NH3.js → use-builds-data-J-riQYyC.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-DL2TmWZ0.js → use-password-reset-mode-C8-eH-UG.js} +1 -1
- package/lib/public/assets/{users-CqBuxMlX.js → users-CFba5TSy.js} +1 -1
- package/lib/public/assets/{users-DjQSucKb.js → users-Cp0beJm3.js} +1 -1
- package/lib/public/assets/{video-off-T0rlt0GA.js → video-off-DDsEISjV.js} +1 -1
- package/lib/public/assets/webhook-settings-BGu32vSB.js +1 -0
- package/lib/public/assets/{zap-gBHqutZV.js → zap-l4VaXzq8.js} +1 -1
- package/lib/public/index.html +2 -2
- package/lib/schema.json +19 -46
- package/lib/src/XenonCapabilityManager.js +22 -5
- package/lib/src/app/openapi/control.yaml +71 -20
- package/lib/src/app/openapi/grid.yaml +23 -5
- package/lib/src/app/openapi/platform.yaml +136 -28
- package/lib/src/app/routers/control.js +30 -5
- package/lib/src/app/routers/dashboard.js +29 -1
- package/lib/src/app/routers/grid.js +32 -10
- package/lib/src/app/routers/streamStartConflict.js +3 -0
- package/lib/src/app/routers/streamType.js +7 -2
- package/lib/src/app/routers/webhook.js +8 -4
- package/lib/src/config.js +50 -5
- package/lib/src/dashboard/commands.js +185 -141
- package/lib/src/dashboard/event-manager.js +39 -6
- package/lib/src/data-service/appleFamily.js +43 -0
- package/lib/src/data-service/db.js +11 -0
- package/lib/src/data-service/device-service.js +46 -1
- package/lib/src/data-service/device-store.js +66 -3
- package/lib/src/data-service/deviceClaims.js +37 -0
- package/lib/src/data-service/deviceFieldOwners.js +25 -11
- package/lib/src/data-service/deviceSettings.js +79 -0
- package/lib/src/data-service/prisma-store.js +152 -9
- package/lib/src/data-service/web-config-service.js +43 -92
- package/lib/src/device-managers/AndroidDeviceManager.js +9 -31
- package/lib/src/device-managers/ios/WDAClient.js +44 -43
- package/lib/src/device-managers/shellCommands.js +96 -0
- package/lib/src/device-utils.js +143 -37
- package/lib/src/generated/client/edge.js +17 -3
- package/lib/src/generated/client/index-browser.js +14 -0
- package/lib/src/generated/client/index.d.ts +2054 -379
- package/lib/src/generated/client/index.js +17 -3
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +35 -7
- package/lib/src/generated/client/wasm.js +14 -0
- package/lib/src/interceptors/CommandInterceptor.js +29 -8
- package/lib/src/interceptors/xenonScripts.js +55 -0
- package/lib/src/interfaces/IPluginArgs.js +1 -2
- package/lib/src/middleware/deviceAccessGuard.js +63 -9
- package/lib/src/plugin.js +15 -1
- package/lib/src/prisma.js +1 -1
- package/lib/src/scripts/database-check.js +124 -0
- package/lib/src/services/AIService.js +99 -0
- package/lib/src/services/CleanupService.js +6 -1
- package/lib/src/services/InterceptorService.js +70 -13
- package/lib/src/services/NetworkConditioningService.js +85 -88
- package/lib/src/services/NotificationService.js +36 -29
- package/lib/src/services/OrphanSweeper.js +5 -0
- package/lib/src/services/PortAllocator.js +5 -0
- package/lib/src/services/ServerManager.js +97 -8
- package/lib/src/services/SessionLifecycleService.js +63 -53
- package/lib/src/services/ShutdownCoordinator.js +33 -0
- package/lib/src/services/TeamService.js +20 -3
- package/lib/src/services/device-access/SessionOwnerResolver.js +26 -0
- package/lib/src/services/device-access/deviceAccessPolicy.js +38 -6
- package/lib/src/services/device-access/leaseHold.js +26 -0
- package/lib/src/services/device-access/ticketActorAccess.js +6 -0
- package/lib/src/services/healing/HealingOrchestrator.js +32 -1
- package/lib/src/services/interceptor/AndroidProxyAdapter.js +18 -0
- package/lib/src/services/interceptor/interceptorOptions.js +44 -0
- package/lib/src/services/lease/activeLeases.js +37 -0
- package/lib/src/services/network/AndroidRadios.js +71 -0
- package/lib/src/services/network/PhoneNetworkLedger.js +184 -0
- package/lib/src/services/network/PhoneNetworkRestore.js +330 -0
- package/lib/src/services/network/adbForPhone.js +60 -0
- package/lib/src/services/network/phoneNetworkLock.js +20 -0
- package/lib/src/services/network/xenonProxy.js +107 -0
- package/lib/src/services/omni-vision/OmniVisionService.js +53 -43
- package/lib/src/services/omni-vision/VisionAssertionService.js +28 -25
- package/lib/src/services/omni-vision/ocrTextMatch.js +105 -0
- package/lib/src/services/omni-vision/screenScale.js +68 -0
- package/lib/src/services/recording/busy-precheck.js +24 -3
- package/lib/src/services/recording/concurrency-gate.js +12 -3
- package/lib/src/services/settings/SelfHealingSwitch.js +70 -0
- package/lib/src/services/settings/labSettings.js +149 -0
- package/lib/src/services/webhookEvents.js +173 -0
- package/lib/test/helpers/fake-adb.js +58 -0
- package/lib/test/helpers/fake-device-table.js +63 -24
- package/lib/test/helpers/scratch-database.js +1 -0
- package/lib/test/unit/TeamService.test.js +3 -0
- package/lib/test/unit/XenonCapabilityManager.spec.js +4 -2
- package/lib/test/unit/apple-family.spec.js +36 -0
- package/lib/test/unit/cleanup-settings-db.spec.js +103 -0
- package/lib/test/unit/cleanup-settings.spec.js +244 -0
- package/lib/test/unit/dashboard-api-paths.spec.js +101 -0
- package/lib/test/unit/dashboard-commands.spec.js +249 -0
- package/lib/test/unit/dashboard-events-team-scope.spec.js +9 -6
- package/lib/test/unit/database-check.spec.js +108 -0
- package/lib/test/unit/default-plugin-args.spec.js +33 -0
- package/lib/test/unit/device-access-lease.spec.js +270 -0
- package/lib/test/unit/device-offline-webhook.spec.js +81 -0
- package/lib/test/unit/device-setting-columns.spec.js +66 -0
- package/lib/test/unit/device-settings-loki.spec.js +87 -0
- package/lib/test/unit/device-settings-survive.spec.js +555 -0
- package/lib/test/unit/device-sync-lost-update.spec.js +5 -0
- package/lib/test/unit/emulator-boot.spec.js +91 -0
- package/lib/test/unit/hub-node-gateway-appium-server.spec.js +48 -0
- package/lib/test/unit/hub-node-same-host.spec.js +5 -0
- package/lib/test/unit/iphone-ipad-only.spec.js +132 -0
- package/lib/test/unit/json-logging-option.spec.js +74 -0
- package/lib/test/unit/lab-settings.spec.js +218 -0
- package/lib/test/unit/lease/allocate-lease-branch.spec.js +22 -0
- package/lib/test/unit/lease/session-lease-proof.spec.js +59 -0
- package/lib/test/unit/lease-held-phone.spec.js +196 -0
- package/lib/test/unit/max-sessions.spec.js +240 -0
- package/lib/test/unit/ocr-text-match.spec.js +44 -0
- package/lib/test/unit/omni-vision-analyze-screen.spec.js +72 -0
- package/lib/test/unit/omni-vision-ios-points.spec.js +161 -0
- package/lib/test/unit/omni-vision-tap-text.spec.js +129 -0
- package/lib/test/unit/omni-vision.spec.js +1 -1
- package/lib/test/unit/partial-config-file.spec.js +114 -0
- package/lib/test/unit/phone-network/boot-clean-up.spec.js +64 -0
- package/lib/test/unit/phone-network/interceptor-options.spec.js +77 -0
- package/lib/test/unit/phone-network/interceptor-proxy-restore.spec.js +145 -0
- package/lib/test/unit/phone-network/network-conditioning.spec.js +145 -0
- package/lib/test/unit/phone-network/phone-network-restore.spec.js +246 -0
- package/lib/test/unit/phone-network/session-end-paths.spec.js +182 -0
- package/lib/test/unit/phone-network/session-start.spec.js +110 -0
- package/lib/test/unit/phone-network/xenon-proxy.spec.js +69 -0
- package/lib/test/unit/plugin.spec.js +3 -3
- package/lib/test/unit/recording-options.spec.js +181 -0
- package/lib/test/unit/schema-required-args.spec.js +21 -16
- package/lib/test/unit/self-healing-boot.spec.js +75 -0
- package/lib/test/unit/self-healing-setting.spec.js +272 -0
- package/lib/test/unit/session-failed-webhook.spec.js +170 -0
- package/lib/test/unit/shell-commands.spec.js +133 -0
- package/lib/test/unit/socketServer.scopedEmit.spec.js +2 -0
- package/lib/test/unit/stream-start-pipelines.spec.js +210 -0
- package/lib/test/unit/stream-status-type.spec.js +4 -0
- package/lib/test/unit/vision-assertion.spec.js +118 -0
- package/lib/test/unit/wda-client-shell.spec.js +133 -0
- package/lib/test/unit/web-config-service.spec.js +79 -0
- package/lib/test/unit/webhook-delivery.spec.js +24 -0
- package/lib/test/unit/webhook-payloads.spec.js +273 -0
- package/lib/test/unit/xenon-execute-scripts.spec.js +138 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/web/src/components/webhook-settings/webhookEventVariables.json +30 -0
- package/package.json +1 -1
- package/prisma/migrations/20261004140000_keep_device_settings/migration.sql +19 -0
- package/prisma/schema.prisma +35 -7
- package/schema.json +19 -46
- package/scripts/generate-types-from-schema.js +7 -2
- package/scripts/lib/always-present-plugin-args.js +42 -0
- package/lib/public/assets/DeviceMosaicView-ChhHnpFQ.js +0 -2
- package/lib/public/assets/device-explorer-DSpAA_t9.js +0 -279
- package/lib/public/assets/maintenance-settings-BxbA7SkB.js +0 -6
- package/lib/public/assets/recording-group-store-B75435xR.js +0 -26
- package/lib/public/assets/session-detail-page-Bc02YjMm.js +0 -4
- package/lib/public/assets/settings-C1VIluuT.js +0 -6
- package/lib/public/assets/webhook-settings-DAHgnJyZ.js +0 -1
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
|
+
};
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
require("reflect-metadata");
|
|
49
|
+
const events_1 = require("events");
|
|
50
|
+
const chai_1 = require("chai");
|
|
51
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
52
|
+
const express_1 = __importDefault(require("express"));
|
|
53
|
+
const typedi_1 = require("typedi");
|
|
54
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
55
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
56
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
57
|
+
const own_node_id_1 = require("../helpers/own-node-id");
|
|
58
|
+
const grid_1 = __importDefault(require("../../src/app/routers/grid"));
|
|
59
|
+
const reservation_1 = __importDefault(require("../../src/app/routers/reservation"));
|
|
60
|
+
const prisma_1 = require("../../src/prisma");
|
|
61
|
+
const prisma_store_1 = require("../../src/data-service/prisma-store");
|
|
62
|
+
const device_store_1 = require("../../src/data-service/device-store");
|
|
63
|
+
const device_service_1 = require("../../src/data-service/device-service");
|
|
64
|
+
const device_utils_1 = require("../../src/device-utils");
|
|
65
|
+
const helpers = __importStar(require("../../src/helpers"));
|
|
66
|
+
const AndroidDeviceManager_1 = __importDefault(require("../../src/device-managers/AndroidDeviceManager"));
|
|
67
|
+
const IOSDiscoveryService_1 = require("../../src/device-managers/ios/IOSDiscoveryService");
|
|
68
|
+
const iOSTracker_1 = require("../../src/device-managers/iOSTracker");
|
|
69
|
+
const device_managers_1 = require("../../src/device-managers");
|
|
70
|
+
const localDeviceHosts_1 = require("../../src/device-managers/localDeviceHosts");
|
|
71
|
+
const deviceSettings_1 = require("../../src/data-service/deviceSettings");
|
|
72
|
+
const PluginContext_1 = require("../../src/PluginContext");
|
|
73
|
+
const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
|
|
74
|
+
const SocketServer_1 = require("../../src/services/SocketServer");
|
|
75
|
+
const NotificationService_1 = require("../../src/services/NotificationService");
|
|
76
|
+
const TeamService_1 = require("../../src/services/TeamService");
|
|
77
|
+
/**
|
|
78
|
+
* A phone's Device row is deleted whenever the phone goes: unplugged,
|
|
79
|
+
* rebooted, offline or unauthorized in adb, an iPhone detached, a simulator
|
|
80
|
+
* no longer reported, a restart of the server, and on a hub its node gone or
|
|
81
|
+
* missing one health probe. The row was the only place an admin's settings
|
|
82
|
+
* for the phone lived (team, tags, maintenance, reservation), so each of
|
|
83
|
+
* those reset them. A team is a device boundary: a team's phone came back in
|
|
84
|
+
* the shared pool, visible to every member.
|
|
85
|
+
*
|
|
86
|
+
* These run the real PrismaDeviceStore on a scratch database.
|
|
87
|
+
*/
|
|
88
|
+
const IP = '10.0.0.1';
|
|
89
|
+
const OWN = `http://${IP}:4723`;
|
|
90
|
+
const NODE = `http://${IP}:4725`;
|
|
91
|
+
const UDID = 'R5CT32ABCDE';
|
|
92
|
+
const HOUR = 60 * 60 * 1000;
|
|
93
|
+
const ADMIN = { userId: 'usr_admin', role: 'ADMIN', scopes: 'admin,devices,sessions,read' };
|
|
94
|
+
const MEMBER = {
|
|
95
|
+
userId: 'usr_member',
|
|
96
|
+
role: 'MEMBER',
|
|
97
|
+
scopes: 'devices,sessions,read',
|
|
98
|
+
teamIds: [],
|
|
99
|
+
};
|
|
100
|
+
const phone = (udid, host = OWN, over = {}) => (Object.assign({ udid,
|
|
101
|
+
host, name: `Phone ${udid}`, platform: 'android', deviceType: 'real', realDevice: true, state: 'device', sdk: '14', busy: false, userBlocked: false, totalUtilizationTimeMilliSec: 0, sessionStartTime: 0, nodeId: host === OWN ? own_node_id_1.OWN_NODE_ID : 'node-1' }, over));
|
|
102
|
+
function appAs(caller, args) {
|
|
103
|
+
const app = (0, express_1.default)();
|
|
104
|
+
app.use(express_1.default.json());
|
|
105
|
+
app.use((req, _res, next) => {
|
|
106
|
+
req.auth = Object.assign({ kind: 'user-session', rateLimit: 1000 }, caller);
|
|
107
|
+
next();
|
|
108
|
+
});
|
|
109
|
+
const router = express_1.default.Router();
|
|
110
|
+
grid_1.default.register(router, args);
|
|
111
|
+
router.use('/reservation', reservation_1.default);
|
|
112
|
+
app.use(router);
|
|
113
|
+
return app;
|
|
114
|
+
}
|
|
115
|
+
describe('An admin’s settings for a phone outlive its Device row', () => {
|
|
116
|
+
const scratch = (0, scratch_database_1.useScratchDatabase)();
|
|
117
|
+
let args;
|
|
118
|
+
let context;
|
|
119
|
+
let savedContext;
|
|
120
|
+
let savedStore;
|
|
121
|
+
let restoreContainer;
|
|
122
|
+
let events;
|
|
123
|
+
let discover;
|
|
124
|
+
let usbmux;
|
|
125
|
+
const local = () => (0, localDeviceHosts_1.localDeviceHosts)(args, 4723);
|
|
126
|
+
const admin = () => appAs(ADMIN, args);
|
|
127
|
+
const member = () => appAs(MEMBER, args);
|
|
128
|
+
const row = (udid = UDID, host = OWN) => device_store_1.DeviceStoreFactory.getStore().findDevice({ udid, host });
|
|
129
|
+
/** Team, tags and maintenance, through the routes the dashboard calls. */
|
|
130
|
+
function configure() {
|
|
131
|
+
return __awaiter(this, arguments, void 0, function* (udid = UDID, host = OWN) {
|
|
132
|
+
const team = yield (0, loopbackRequest_1.default)(admin()).put(`/device/${udid}/team`).send({ teamId: 'team-a' });
|
|
133
|
+
(0, chai_1.expect)(team.status, JSON.stringify(team.body)).to.equal(200);
|
|
134
|
+
const tags = yield (0, loopbackRequest_1.default)(admin())
|
|
135
|
+
.post('/device/tags')
|
|
136
|
+
.send({ udid, host, tags: ['lab-1', 'smoke'] });
|
|
137
|
+
(0, chai_1.expect)(tags.status, JSON.stringify(tags.body)).to.equal(200);
|
|
138
|
+
const block = yield (0, loopbackRequest_1.default)(admin()).post('/block').send({ udid, host });
|
|
139
|
+
(0, chai_1.expect)(block.status, JSON.stringify(block.body)).to.equal(200);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function expectConfigured(device) {
|
|
143
|
+
(0, chai_1.expect)(device, 'the phone is back').to.not.equal(null);
|
|
144
|
+
(0, chai_1.expect)(device).to.include({ teamId: 'team-a', userBlocked: true });
|
|
145
|
+
(0, chai_1.expect)(device === null || device === void 0 ? void 0 : device.tags).to.deep.equal(['lab-1', 'smoke']);
|
|
146
|
+
}
|
|
147
|
+
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
148
|
+
events = [];
|
|
149
|
+
discover = () => __awaiter(void 0, void 0, void 0, function* () { return []; });
|
|
150
|
+
usbmux = new events_1.EventEmitter();
|
|
151
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(SocketServer_1.SocketServer, NotificationService_1.NotificationService, device_managers_1.XenonManager, iOSTracker_1.IosTracker);
|
|
152
|
+
typedi_1.Container.set({
|
|
153
|
+
id: SocketServer_1.SocketServer,
|
|
154
|
+
value: {
|
|
155
|
+
emitToDashboard: () => undefined,
|
|
156
|
+
emitToDashboardForDevices: (event, data, scope) => __awaiter(void 0, void 0, void 0, function* () {
|
|
157
|
+
events.push({ event, data, scope });
|
|
158
|
+
}),
|
|
159
|
+
hasScopedDashboard: () => false,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
typedi_1.Container.set({ id: NotificationService_1.NotificationService, value: { dispatchEvent: () => __awaiter(void 0, void 0, void 0, function* () { return undefined; }) } });
|
|
163
|
+
typedi_1.Container.set({
|
|
164
|
+
id: device_managers_1.XenonManager,
|
|
165
|
+
value: { getDevices: (known) => discover(known) },
|
|
166
|
+
});
|
|
167
|
+
typedi_1.Container.set({ id: iOSTracker_1.IosTracker, value: { getListener: () => usbmux } });
|
|
168
|
+
args = Object.assign(Object.assign({}, IPluginArgs_1.DefaultPluginArgs), { bindHostOrIp: IP });
|
|
169
|
+
context = typedi_1.Container.get(PluginContext_1.PluginContext);
|
|
170
|
+
savedContext = Object.assign({}, context);
|
|
171
|
+
context.setContext(args, 4723, own_node_id_1.OWN_NODE_ID, '');
|
|
172
|
+
savedStore = device_store_1.DeviceStoreFactory._deviceStore;
|
|
173
|
+
device_store_1.DeviceStoreFactory._deviceStore = new prisma_store_1.PrismaDeviceStore();
|
|
174
|
+
yield scratch.db.deviceSetting.deleteMany({});
|
|
175
|
+
yield scratch.db.device.deleteMany({});
|
|
176
|
+
yield scratch.db.team.deleteMany({});
|
|
177
|
+
yield scratch.db.team.create({ data: { id: 'team-a', name: 'Team A' } });
|
|
178
|
+
yield scratch.db.team.create({ data: { id: 'team-b', name: 'Team B' } });
|
|
179
|
+
}));
|
|
180
|
+
afterEach(() => {
|
|
181
|
+
device_store_1.DeviceStoreFactory._deviceStore = savedStore;
|
|
182
|
+
Object.assign(context, savedContext);
|
|
183
|
+
sinon_1.default.restore();
|
|
184
|
+
restoreContainer();
|
|
185
|
+
});
|
|
186
|
+
describe('on this server’s own phones', () => {
|
|
187
|
+
it('keeps them when the phone is unplugged and plugged back in', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
188
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
189
|
+
yield configure();
|
|
190
|
+
yield (0, device_service_1.removeDevice)([{ udid: UDID, host: OWN }]);
|
|
191
|
+
(0, chai_1.expect)(yield row()).to.equal(null);
|
|
192
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
193
|
+
expectConfigured(yield row());
|
|
194
|
+
}));
|
|
195
|
+
it('keeps them when adb reports the phone offline, then back', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
196
|
+
const manager = new AndroidDeviceManager_1.default(context);
|
|
197
|
+
sinon_1.default.stub(manager, 'waitBootComplete').resolves();
|
|
198
|
+
sinon_1.default.stub(manager, 'isRealDevice').resolves(true);
|
|
199
|
+
sinon_1.default.stub(manager, 'deviceInfo').callsFake((d) => __awaiter(void 0, void 0, void 0, function* () { return phone(d.udid); }));
|
|
200
|
+
const tracker = new events_1.EventEmitter();
|
|
201
|
+
yield manager.createLocalAdbTracker(tracker, {})();
|
|
202
|
+
const change = tracker.listeners('change')[0];
|
|
203
|
+
yield change({ id: UDID, type: 'device' });
|
|
204
|
+
yield configure();
|
|
205
|
+
yield change({ id: UDID, type: 'offline' });
|
|
206
|
+
(0, chai_1.expect)(yield row()).to.equal(null);
|
|
207
|
+
yield change({ id: UDID, type: 'device' });
|
|
208
|
+
expectConfigured(yield row());
|
|
209
|
+
}));
|
|
210
|
+
it('keeps them when an iPhone is detached and attached again', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
211
|
+
const ios = new IOSDiscoveryService_1.IOSDiscoveryService(context);
|
|
212
|
+
sinon_1.default
|
|
213
|
+
.stub(ios, 'getDeviceInfo')
|
|
214
|
+
.callsFake((udid) => __awaiter(void 0, void 0, void 0, function* () { return phone(udid, OWN, { platform: 'ios' }); }));
|
|
215
|
+
ios.trackIOSDevices();
|
|
216
|
+
const attached = usbmux.listeners('attached')[0];
|
|
217
|
+
const detached = usbmux.listeners('detached')[0];
|
|
218
|
+
yield attached('iphone-1');
|
|
219
|
+
yield configure('iphone-1');
|
|
220
|
+
yield detached('iphone-1');
|
|
221
|
+
(0, chai_1.expect)(yield row('iphone-1')).to.equal(null);
|
|
222
|
+
yield attached('iphone-1');
|
|
223
|
+
expectConfigured(yield row('iphone-1'));
|
|
224
|
+
}));
|
|
225
|
+
it('keeps them when discovery stops reporting the phone, then reports it again', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
226
|
+
const sim = phone('sim-1', OWN, { platform: 'ios', deviceType: 'simulator' });
|
|
227
|
+
discover = () => __awaiter(void 0, void 0, void 0, function* () { return [sim]; });
|
|
228
|
+
yield (0, device_utils_1.updateDeviceList)(local());
|
|
229
|
+
yield configure('sim-1');
|
|
230
|
+
discover = () => __awaiter(void 0, void 0, void 0, function* () { return []; }); // shut down, with bootedSimulators on
|
|
231
|
+
yield (0, device_utils_1.updateDeviceList)(local());
|
|
232
|
+
(0, chai_1.expect)(yield row('sim-1')).to.equal(null);
|
|
233
|
+
discover = () => __awaiter(void 0, void 0, void 0, function* () { return [sim]; });
|
|
234
|
+
yield (0, device_utils_1.updateDeviceList)(local());
|
|
235
|
+
expectConfigured(yield row('sim-1'));
|
|
236
|
+
}));
|
|
237
|
+
it('keeps them across a restart of the server', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
238
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
239
|
+
yield configure();
|
|
240
|
+
yield (0, deviceSettings_1.resetDevicesAtBoot)(device_store_1.DeviceStoreFactory.getStore(), args, local());
|
|
241
|
+
(0, chai_1.expect)(yield row()).to.equal(null);
|
|
242
|
+
discover = () => __awaiter(void 0, void 0, void 0, function* () { return [phone(UDID)]; });
|
|
243
|
+
yield (0, device_utils_1.updateDeviceList)(local());
|
|
244
|
+
expectConfigured(yield row());
|
|
245
|
+
}));
|
|
246
|
+
it('saves, at the first start after an upgrade, the settings rows kept only on the row', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
247
|
+
// Written before this change: the settings live on the row alone.
|
|
248
|
+
yield scratch.db.device.create({
|
|
249
|
+
data: {
|
|
250
|
+
udid: UDID,
|
|
251
|
+
host: OWN,
|
|
252
|
+
nodeId: 'the-previous-boot',
|
|
253
|
+
platform: 'android',
|
|
254
|
+
teamId: 'team-a',
|
|
255
|
+
tags: JSON.stringify(['lab-1', 'smoke']),
|
|
256
|
+
userBlocked: true,
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
yield (0, deviceSettings_1.resetDevicesAtBoot)(device_store_1.DeviceStoreFactory.getStore(), args, local());
|
|
260
|
+
(0, chai_1.expect)(yield row()).to.equal(null);
|
|
261
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
262
|
+
expectConfigured(yield row());
|
|
263
|
+
}));
|
|
264
|
+
it('starts the phone in its team in the same write: a member never sees it in between', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
265
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
266
|
+
yield configure();
|
|
267
|
+
yield (0, device_service_1.removeDevice)([{ udid: UDID, host: OWN }]);
|
|
268
|
+
// useScratchDatabase stubs the delegate already: read its calls.
|
|
269
|
+
const upsert = prisma_1.prisma.device.upsert;
|
|
270
|
+
upsert.resetHistory();
|
|
271
|
+
events = [];
|
|
272
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
273
|
+
// The row is created with its team: no later write puts it there.
|
|
274
|
+
(0, chai_1.expect)(upsert.callCount).to.equal(1);
|
|
275
|
+
(0, chai_1.expect)(upsert.firstCall.args[0].create).to.include({ teamId: 'team-a' });
|
|
276
|
+
// The dashboards that may see it are told, and only those.
|
|
277
|
+
const added = events.find((e) => e.event === 'device_added');
|
|
278
|
+
(0, chai_1.expect)(added === null || added === void 0 ? void 0 : added.scope).to.deep.equal({ udid: UDID, teamId: 'team-a' });
|
|
279
|
+
// A member of no team doesn't get it from the device list.
|
|
280
|
+
const list = yield (0, loopbackRequest_1.default)(member()).get('/devices');
|
|
281
|
+
(0, chai_1.expect)(list.status).to.equal(200);
|
|
282
|
+
(0, chai_1.expect)(list.body.map((d) => d.udid)).to.not.include(UDID);
|
|
283
|
+
}));
|
|
284
|
+
it('keeps a reservation until it ends, and not after', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
285
|
+
var _a, _b, _c;
|
|
286
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID), phone('other')]);
|
|
287
|
+
for (const udid of [UDID, 'other']) {
|
|
288
|
+
const r = yield (0, loopbackRequest_1.default)(admin())
|
|
289
|
+
.post('/reservation')
|
|
290
|
+
.send({ udid, host: OWN, reservedBy: 'Dana', duration: '1h', reason: 'release' });
|
|
291
|
+
(0, chai_1.expect)(r.status, JSON.stringify(r.body)).to.equal(200);
|
|
292
|
+
}
|
|
293
|
+
// 'other' ends while it is unplugged.
|
|
294
|
+
yield (0, device_service_1.removeDevice)([
|
|
295
|
+
{ udid: UDID, host: OWN },
|
|
296
|
+
{ udid: 'other', host: OWN },
|
|
297
|
+
]);
|
|
298
|
+
const now = Date.now();
|
|
299
|
+
sinon_1.default.stub(Date, 'now').returns(now + 30 * 60 * 1000);
|
|
300
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
301
|
+
Date.now.returns(now + 2 * HOUR);
|
|
302
|
+
yield (0, device_service_1.addNewDevice)([phone('other')]);
|
|
303
|
+
(0, chai_1.expect)(yield row()).to.include({
|
|
304
|
+
reservedBy: 'Dana',
|
|
305
|
+
reservedByUserId: 'usr_admin',
|
|
306
|
+
reservationReason: 'release',
|
|
307
|
+
});
|
|
308
|
+
(0, chai_1.expect)((_a = (yield row())) === null || _a === void 0 ? void 0 : _a.reservedUntil).to.be.closeTo(now + HOUR, 5000);
|
|
309
|
+
const other = yield row('other');
|
|
310
|
+
(0, chai_1.expect)((_b = other === null || other === void 0 ? void 0 : other.reservedBy) !== null && _b !== void 0 ? _b : null).to.equal(null);
|
|
311
|
+
(0, chai_1.expect)((_c = other === null || other === void 0 ? void 0 : other.reservedUntil) !== null && _c !== void 0 ? _c : null).to.equal(null);
|
|
312
|
+
}));
|
|
313
|
+
it('keeps what was cleared cleared', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
314
|
+
var _a, _b;
|
|
315
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
316
|
+
yield configure();
|
|
317
|
+
(0, chai_1.expect)((yield (0, loopbackRequest_1.default)(admin()).put(`/device/${UDID}/team`).send({ teamId: null })).status).to.equal(200);
|
|
318
|
+
(0, chai_1.expect)((yield (0, loopbackRequest_1.default)(admin()).post('/device/tags').send({ udid: UDID, host: OWN, tags: [] }))
|
|
319
|
+
.status).to.equal(200);
|
|
320
|
+
(0, chai_1.expect)((yield (0, loopbackRequest_1.default)(admin()).post('/unblock').send({ udid: UDID, host: OWN })).status).to.equal(200);
|
|
321
|
+
yield (0, device_service_1.removeDevice)([{ udid: UDID, host: OWN }]);
|
|
322
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
323
|
+
const back = yield row();
|
|
324
|
+
(0, chai_1.expect)((_a = back === null || back === void 0 ? void 0 : back.teamId) !== null && _a !== void 0 ? _a : null).to.equal(null);
|
|
325
|
+
(0, chai_1.expect)(back === null || back === void 0 ? void 0 : back.userBlocked).to.equal(false);
|
|
326
|
+
(0, chai_1.expect)((_b = back === null || back === void 0 ? void 0 : back.tags) !== null && _b !== void 0 ? _b : []).to.deep.equal([]);
|
|
327
|
+
}));
|
|
328
|
+
});
|
|
329
|
+
describe('on a hub, for a node’s phones', () => {
|
|
330
|
+
const report = (over = {}) => (0, loopbackRequest_1.default)(admin())
|
|
331
|
+
.post('/register?type=add')
|
|
332
|
+
.send([phone('node-phone', NODE, over)]);
|
|
333
|
+
it('keeps them when the node unregisters and registers again', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
334
|
+
(0, chai_1.expect)((yield report()).status).to.equal(200);
|
|
335
|
+
yield configure('node-phone', NODE);
|
|
336
|
+
const gone = yield (0, loopbackRequest_1.default)(admin()).post(`/register?type=unregister&host=${encodeURIComponent(NODE)}&nodeId=node-1`);
|
|
337
|
+
(0, chai_1.expect)(gone.status).to.equal(200);
|
|
338
|
+
(0, chai_1.expect)(yield row('node-phone', NODE)).to.equal(null);
|
|
339
|
+
(0, chai_1.expect)((yield report()).status).to.equal(200);
|
|
340
|
+
expectConfigured(yield row('node-phone', NODE));
|
|
341
|
+
}));
|
|
342
|
+
it('keeps them when the node misses a health probe', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
343
|
+
(0, chai_1.expect)((yield report()).status).to.equal(200);
|
|
344
|
+
yield configure('node-phone', NODE);
|
|
345
|
+
sinon_1.default.stub(helpers, 'isXenonRunning').resolves(false);
|
|
346
|
+
yield (0, device_utils_1.removeStaleDevices)(local());
|
|
347
|
+
(0, chai_1.expect)(yield row('node-phone', NODE)).to.equal(null);
|
|
348
|
+
(0, chai_1.expect)((yield report()).status).to.equal(200);
|
|
349
|
+
expectConfigured(yield row('node-phone', NODE));
|
|
350
|
+
}));
|
|
351
|
+
it('takes no setting from the node’s report, on a new row or a known one', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
352
|
+
var _a;
|
|
353
|
+
const own = { teamId: 'team-b', tags: ['from-node'], userBlocked: true };
|
|
354
|
+
(0, chai_1.expect)((yield report(own)).status).to.equal(200);
|
|
355
|
+
let hubRow = yield row('node-phone', NODE);
|
|
356
|
+
(0, chai_1.expect)((_a = hubRow === null || hubRow === void 0 ? void 0 : hubRow.teamId) !== null && _a !== void 0 ? _a : null).to.equal(null);
|
|
357
|
+
(0, chai_1.expect)(hubRow === null || hubRow === void 0 ? void 0 : hubRow.userBlocked).to.equal(false);
|
|
358
|
+
yield configure('node-phone', NODE);
|
|
359
|
+
yield (0, loopbackRequest_1.default)(admin()).post('/unblock').send({ udid: 'node-phone', host: NODE });
|
|
360
|
+
yield (0, loopbackRequest_1.default)(admin()).post(`/register?type=unregister&host=${encodeURIComponent(NODE)}&nodeId=node-1`);
|
|
361
|
+
(0, chai_1.expect)((yield report(own)).status).to.equal(200);
|
|
362
|
+
hubRow = yield row('node-phone', NODE);
|
|
363
|
+
(0, chai_1.expect)(hubRow).to.include({ teamId: 'team-a', userBlocked: false });
|
|
364
|
+
(0, chai_1.expect)(hubRow === null || hubRow === void 0 ? void 0 : hubRow.tags).to.deep.equal(['lab-1', 'smoke']);
|
|
365
|
+
}));
|
|
366
|
+
it('never gives a node’s phone the settings of the hub’s own phone with that udid', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
367
|
+
var _a;
|
|
368
|
+
// emulator-5554 is on every machine that runs an emulator.
|
|
369
|
+
yield (0, device_service_1.addNewDevice)([phone('emulator-5554')]);
|
|
370
|
+
yield (0, loopbackRequest_1.default)(admin())
|
|
371
|
+
.post('/device/tags')
|
|
372
|
+
.send({ udid: 'emulator-5554', host: OWN, tags: ['hub-only'] });
|
|
373
|
+
yield (0, loopbackRequest_1.default)(admin()).post('/block').send({ udid: 'emulator-5554', host: OWN });
|
|
374
|
+
const r = yield (0, loopbackRequest_1.default)(admin())
|
|
375
|
+
.post('/register?type=add')
|
|
376
|
+
.send([phone('emulator-5554', NODE)]);
|
|
377
|
+
(0, chai_1.expect)(r.status).to.equal(200);
|
|
378
|
+
const nodeRow = yield row('emulator-5554', NODE);
|
|
379
|
+
(0, chai_1.expect)(nodeRow === null || nodeRow === void 0 ? void 0 : nodeRow.userBlocked).to.equal(false);
|
|
380
|
+
(0, chai_1.expect)((_a = nodeRow === null || nodeRow === void 0 ? void 0 : nodeRow.tags) !== null && _a !== void 0 ? _a : null).to.equal(null);
|
|
381
|
+
}));
|
|
382
|
+
it('keeps them across a restart of the hub, which keeps its nodes’ phones', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
383
|
+
(0, chai_1.expect)((yield report()).status).to.equal(200);
|
|
384
|
+
yield configure('node-phone', NODE);
|
|
385
|
+
yield (0, deviceSettings_1.resetDevicesAtBoot)(device_store_1.DeviceStoreFactory.getStore(), args, local());
|
|
386
|
+
expectConfigured(yield row('node-phone', NODE));
|
|
387
|
+
}));
|
|
388
|
+
});
|
|
389
|
+
describe('what the dashboard and the API save', () => {
|
|
390
|
+
const saved = (udid = UDID, host = OWN) => scratch.db.deviceSetting.findUnique({ where: { udid_host: { udid, host } } });
|
|
391
|
+
it('saves a team on every phone of the udid, connected or not', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
392
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID), phone(UDID, NODE)]);
|
|
393
|
+
yield scratch.db.deviceSetting.create({ data: { udid: UDID, host: 'http://10.0.0.9:4723' } });
|
|
394
|
+
const r = yield (0, loopbackRequest_1.default)(admin()).put(`/device/${UDID}/team`).send({ teamId: 'team-a' });
|
|
395
|
+
(0, chai_1.expect)(r.status).to.equal(200);
|
|
396
|
+
(0, chai_1.expect)(r.body).to.deep.equal({ ok: true, updated: 3 });
|
|
397
|
+
for (const host of [OWN, NODE, 'http://10.0.0.9:4723']) {
|
|
398
|
+
(0, chai_1.expect)(yield saved(UDID, host), host).to.include({ teamId: 'team-a' });
|
|
399
|
+
}
|
|
400
|
+
(0, chai_1.expect)(yield row(UDID, OWN)).to.include({ teamId: 'team-a' });
|
|
401
|
+
(0, chai_1.expect)(yield row(UDID, NODE)).to.include({ teamId: 'team-a' });
|
|
402
|
+
}));
|
|
403
|
+
it('answers 404 for a udid it has neither a row nor saved settings for', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
404
|
+
const r = yield (0, loopbackRequest_1.default)(admin()).put('/device/nobody/team').send({ teamId: 'team-a' });
|
|
405
|
+
(0, chai_1.expect)(r.status).to.equal(404);
|
|
406
|
+
(0, chai_1.expect)(yield scratch.db.deviceSetting.count()).to.equal(0);
|
|
407
|
+
}));
|
|
408
|
+
it('saves tags and maintenance on the phone named, and only for a phone it has', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
409
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID), phone(UDID, NODE)]);
|
|
410
|
+
yield (0, loopbackRequest_1.default)(admin())
|
|
411
|
+
.post('/device/tags')
|
|
412
|
+
.send({ udid: UDID, host: OWN, tags: ['lab-1'] });
|
|
413
|
+
yield (0, loopbackRequest_1.default)(admin()).post('/block').send({ udid: UDID, host: OWN });
|
|
414
|
+
const unknown = yield (0, loopbackRequest_1.default)(admin())
|
|
415
|
+
.post('/device/tags')
|
|
416
|
+
.send({ udid: 'nobody', host: OWN, tags: ['x'] });
|
|
417
|
+
(0, chai_1.expect)(yield saved()).to.include({ tags: '["lab-1"]', userBlocked: true, teamId: null });
|
|
418
|
+
(0, chai_1.expect)(yield saved(UDID, NODE)).to.equal(null);
|
|
419
|
+
(0, chai_1.expect)(unknown.status).to.equal(404);
|
|
420
|
+
(0, chai_1.expect)(yield saved('nobody')).to.equal(null);
|
|
421
|
+
yield (0, loopbackRequest_1.default)(admin()).post('/unblock').send({ udid: UDID, host: OWN });
|
|
422
|
+
(0, chai_1.expect)(yield saved()).to.include({ tags: '["lab-1"]', userBlocked: false });
|
|
423
|
+
}));
|
|
424
|
+
it('saves a reservation, its extension and its release', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
425
|
+
var _a;
|
|
426
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
427
|
+
const host = encodeURIComponent(OWN);
|
|
428
|
+
yield (0, loopbackRequest_1.default)(member())
|
|
429
|
+
.post('/reservation')
|
|
430
|
+
.send({ udid: UDID, host: OWN, reservedBy: 'Dana', duration: '1h', reason: 'release' });
|
|
431
|
+
const taken = yield saved();
|
|
432
|
+
(0, chai_1.expect)(taken).to.include({
|
|
433
|
+
reservedBy: 'Dana',
|
|
434
|
+
reservedByUserId: 'usr_member',
|
|
435
|
+
reservationReason: 'release',
|
|
436
|
+
});
|
|
437
|
+
yield (0, loopbackRequest_1.default)(member()).post(`/reservation/${UDID}/${host}/extend`).send({ duration: '1h' });
|
|
438
|
+
(0, chai_1.expect)((_a = (yield saved())) === null || _a === void 0 ? void 0 : _a.reservedUntil).to.be.closeTo(Number(taken === null || taken === void 0 ? void 0 : taken.reservedUntil) + HOUR, 1000);
|
|
439
|
+
yield (0, loopbackRequest_1.default)(member()).delete(`/reservation/${UDID}/${host}`);
|
|
440
|
+
(0, chai_1.expect)(yield saved()).to.include({
|
|
441
|
+
reservedBy: null,
|
|
442
|
+
reservedByUserId: null,
|
|
443
|
+
reservedUntil: null,
|
|
444
|
+
reservationReason: null,
|
|
445
|
+
});
|
|
446
|
+
}));
|
|
447
|
+
it('clears a reservation the sweep ends', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
448
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
449
|
+
yield (0, loopbackRequest_1.default)(member())
|
|
450
|
+
.post('/reservation')
|
|
451
|
+
.send({ udid: UDID, host: OWN, reservedBy: 'Dana', duration: '1h' });
|
|
452
|
+
const later = Date.now() + 2 * HOUR;
|
|
453
|
+
sinon_1.default.stub(Date, 'now').returns(later);
|
|
454
|
+
yield (0, device_service_1.cleanExpiredReservations)();
|
|
455
|
+
(0, chai_1.expect)(yield saved()).to.include({ reservedBy: null, reservedUntil: null });
|
|
456
|
+
}));
|
|
457
|
+
});
|
|
458
|
+
describe('a setting saved while the phone’s row is being made', () => {
|
|
459
|
+
it('is on the new row, and in its event, though it came after the row read the settings', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
460
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
461
|
+
yield configure();
|
|
462
|
+
yield (0, device_service_1.removeDevice)([{ udid: UDID, host: OWN }]);
|
|
463
|
+
// An admin moves the phone to team B after addDevices read its saved
|
|
464
|
+
// settings and before it created the row: the row isn't there to write.
|
|
465
|
+
const upsert = prisma_1.prisma.device.upsert;
|
|
466
|
+
upsert.callsFake((call) => __awaiter(void 0, void 0, void 0, function* () {
|
|
467
|
+
yield device_store_1.DeviceStoreFactory.getStore().updateDevice(UDID, OWN, { teamId: 'team-b' });
|
|
468
|
+
return scratch.db.device.upsert(call);
|
|
469
|
+
}));
|
|
470
|
+
events = [];
|
|
471
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
472
|
+
(0, chai_1.expect)(yield row()).to.include({ teamId: 'team-b', userBlocked: true });
|
|
473
|
+
const added = events.find((e) => e.event === 'device_added');
|
|
474
|
+
(0, chai_1.expect)(added === null || added === void 0 ? void 0 : added.scope).to.deep.equal({ udid: UDID, teamId: 'team-b' });
|
|
475
|
+
}));
|
|
476
|
+
});
|
|
477
|
+
describe('removeDevicesFromDatabaseBeforeRunningThePlugin', () => {
|
|
478
|
+
it('forgets the saved settings of this server’s own phones at start, and keeps its nodes’', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
479
|
+
var _a, _b;
|
|
480
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
481
|
+
yield configure();
|
|
482
|
+
(0, chai_1.expect)((yield (0, loopbackRequest_1.default)(admin())
|
|
483
|
+
.post('/register?type=add')
|
|
484
|
+
.send([phone('node-phone', NODE)])).status).to.equal(200);
|
|
485
|
+
yield configure('node-phone', NODE);
|
|
486
|
+
yield (0, deviceSettings_1.resetDevicesAtBoot)(device_store_1.DeviceStoreFactory.getStore(), Object.assign(Object.assign({}, args), { removeDevicesFromDatabaseBeforeRunningThePlugin: true }), local());
|
|
487
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
488
|
+
const own = yield row();
|
|
489
|
+
(0, chai_1.expect)((_a = own === null || own === void 0 ? void 0 : own.teamId) !== null && _a !== void 0 ? _a : null).to.equal(null);
|
|
490
|
+
(0, chai_1.expect)(own === null || own === void 0 ? void 0 : own.userBlocked).to.equal(false);
|
|
491
|
+
(0, chai_1.expect)((_b = own === null || own === void 0 ? void 0 : own.tags) !== null && _b !== void 0 ? _b : null).to.equal(null);
|
|
492
|
+
expectConfigured(yield row('node-phone', NODE));
|
|
493
|
+
(0, chai_1.expect)(yield scratch.db.deviceSetting.findMany({ select: { udid: true, host: true } })).to.deep.equal([{ udid: 'node-phone', host: NODE }]);
|
|
494
|
+
}));
|
|
495
|
+
it('on a node, forgets those of every phone, as it clears every phone', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
496
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID), phone('other', 'http://10.0.0.1:4799')]);
|
|
497
|
+
yield (0, loopbackRequest_1.default)(admin()).post('/block').send({ udid: UDID, host: OWN });
|
|
498
|
+
yield (0, loopbackRequest_1.default)(admin()).post('/block').send({ udid: 'other', host: 'http://10.0.0.1:4799' });
|
|
499
|
+
yield (0, deviceSettings_1.resetDevicesAtBoot)(device_store_1.DeviceStoreFactory.getStore(), Object.assign(Object.assign({}, args), { hub: 'http://10.0.0.2:4723', removeDevicesFromDatabaseBeforeRunningThePlugin: true }), local());
|
|
500
|
+
(0, chai_1.expect)(yield scratch.db.device.count()).to.equal(0);
|
|
501
|
+
(0, chai_1.expect)(yield scratch.db.deviceSetting.count()).to.equal(0);
|
|
502
|
+
}));
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
describe('Saved phone settings: team deletion', () => {
|
|
506
|
+
const scratch = (0, scratch_database_1.useScratchDatabase)();
|
|
507
|
+
let savedStore;
|
|
508
|
+
let restoreContainer;
|
|
509
|
+
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
510
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(SocketServer_1.SocketServer, NotificationService_1.NotificationService);
|
|
511
|
+
typedi_1.Container.set({
|
|
512
|
+
id: SocketServer_1.SocketServer,
|
|
513
|
+
value: {
|
|
514
|
+
emitToDashboard: () => undefined,
|
|
515
|
+
emitToDashboardForDevices: () => __awaiter(void 0, void 0, void 0, function* () { return undefined; }),
|
|
516
|
+
hasScopedDashboard: () => false,
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
typedi_1.Container.set({ id: NotificationService_1.NotificationService, value: { dispatchEvent: () => __awaiter(void 0, void 0, void 0, function* () { return undefined; }) } });
|
|
520
|
+
savedStore = device_store_1.DeviceStoreFactory._deviceStore;
|
|
521
|
+
device_store_1.DeviceStoreFactory._deviceStore = new prisma_store_1.PrismaDeviceStore();
|
|
522
|
+
yield scratch.db.deviceSetting.deleteMany({});
|
|
523
|
+
yield scratch.db.device.deleteMany({});
|
|
524
|
+
yield scratch.db.team.deleteMany({});
|
|
525
|
+
yield scratch.db.team.create({ data: { id: 'team-a', name: 'Team A' } });
|
|
526
|
+
}));
|
|
527
|
+
afterEach(() => {
|
|
528
|
+
device_store_1.DeviceStoreFactory._deviceStore = savedStore;
|
|
529
|
+
sinon_1.default.restore();
|
|
530
|
+
restoreContainer();
|
|
531
|
+
});
|
|
532
|
+
it('refuses to delete a team one of whose phones is not connected now', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
533
|
+
const args = Object.assign(Object.assign({}, IPluginArgs_1.DefaultPluginArgs), { bindHostOrIp: IP });
|
|
534
|
+
yield (0, device_service_1.addNewDevice)([phone(UDID)]);
|
|
535
|
+
(0, chai_1.expect)((yield (0, loopbackRequest_1.default)(appAs(ADMIN, args)).put(`/device/${UDID}/team`).send({ teamId: 'team-a' }))
|
|
536
|
+
.status).to.equal(200);
|
|
537
|
+
yield (0, device_service_1.removeDevice)([{ udid: UDID, host: OWN }]);
|
|
538
|
+
let refusal;
|
|
539
|
+
try {
|
|
540
|
+
yield typedi_1.Container.get(TeamService_1.TeamService).delete('team-a');
|
|
541
|
+
}
|
|
542
|
+
catch (err) {
|
|
543
|
+
refusal = err;
|
|
544
|
+
}
|
|
545
|
+
(0, chai_1.expect)(refusal === null || refusal === void 0 ? void 0 : refusal.message).to.match(/1 device\(s\) \(1 not connected now\)/);
|
|
546
|
+
(0, chai_1.expect)(yield scratch.db.team.count({ where: { id: 'team-a' } })).to.equal(1);
|
|
547
|
+
// The way out: the phone back in the shared pool, by its udid.
|
|
548
|
+
const back = yield (0, loopbackRequest_1.default)(appAs(ADMIN, args))
|
|
549
|
+
.put(`/device/${UDID}/team`)
|
|
550
|
+
.send({ teamId: null });
|
|
551
|
+
(0, chai_1.expect)(back.status, JSON.stringify(back.body)).to.equal(200);
|
|
552
|
+
yield typedi_1.Container.get(TeamService_1.TeamService).delete('team-a');
|
|
553
|
+
(0, chai_1.expect)(yield scratch.db.team.count({ where: { id: 'team-a' } })).to.equal(0);
|
|
554
|
+
}));
|
|
555
|
+
});
|
|
@@ -68,6 +68,11 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
|
|
|
68
68
|
for (const m of Object.keys(table.delegate)) {
|
|
69
69
|
sinon_1.default.stub(prisma_1.prisma.device, m).callsFake(table.delegate[m]);
|
|
70
70
|
}
|
|
71
|
+
// The phones' saved settings (deviceSettings.ts), so none reach a database.
|
|
72
|
+
const settings = (0, fake_device_table_1.fakeDeviceSettingTable)();
|
|
73
|
+
for (const m of Object.keys(settings.delegate)) {
|
|
74
|
+
sinon_1.default.stub(prisma_1.prisma.deviceSetting, m).callsFake(settings.delegate[m]);
|
|
75
|
+
}
|
|
71
76
|
};
|
|
72
77
|
beforeEach(() => {
|
|
73
78
|
emits = [];
|