Mobile apps derive the scheme from their package name, which may contain
an underscore. It cannot introduce an authority, so accepting it keeps
the rule as strong as the RFC 3986 grammar.
The user was looked up by email across the whole platform, so a client
belonging to one tenant could resolve a user of another. New users were also
placed in the tenant named by the provider response rather than in the tenant
that owns the client.
Both now follow the client's own tenant. System clients keep the platform-wide
behaviour they rely on.
A rejected deep link is otherwise invisible to support. AdminController
reuses the prevUri parameter and cookie names instead of its own copies,
and its redirect target moves into a helper so the read side is covered
by a test.
The scheme is restored from the oauth2_auth_request cookie, which the
client can replace, so checking it only while the authorization request
is built still let a forged cookie point the token redirect at any host.
Both handlers now re-check it on read, sharing the rule with
OAuth2AppTokenFactory.
The success handler keeps prevUri out of the base URL as well, so the
error redirect no longer appends /login to an in-app path.
Loosen LtsMigrationService.select() to pick every bean in the (from, to]
range regardless of family, so a 4.3.x -> 4.4 offline upgrade runs the real
in-range 4.3.1.x beans plus a new 4.4.0.0 baseline bean, each exactly once.
This replaces the ad-hoc explicit V4_3_1_3Migration.apply() call in
DefaultDataUpdateService and moves the 4.4 baseline flat DDL out of
basic/schema_update.sql into V4_4_0_0Migration / lts/4.4.0.0/schema_update.sql.
- Drop the dangling "spec §7" reference from the test comments; inline the
actual rule (a lexical MIN/MAX result over string telemetry stays a string).
- Use a zero-padded code ("0009") for the string-result case - a clearer example
of a genuine string that must not be coerced to a number (would lose padding).
- Define the maxCode metric in the test CF configuration alongside consumption
and avgConsumption, so all parameterized metric names are configured.
Address review feedback on the integration test:
- Read latest telemetry with useStrictDataTypes=true always, so the value node
keeps its stored type (numeric -> JSON number, str_v -> JSON string).
- Assert the aggregation result is a numeric node across all existing scenarios
via a shared assertNumericValue helper, instead of a separate standalone test.
isNumber() is the actual regression guard; asLong()/asText() would coerce a
str_v string and miss it, so the type is asserted explicitly and the value is
compared numerically.
- Drop the redundant standalone test that duplicated an existing scenario.
Cover the fix that makes EntityAggregationCalculatedFieldState.toResult()
emit numeric aggregation results as numeric JSON nodes (dbl_v/long_v) instead
of JSON strings (str_v), which had broken server-side AVG/SUM aggregation.
- New unit test EntityAggregationCalculatedFieldStateTest: asserts toResult()
serializes a numeric result as a numeric node and a genuine string result
(lexical MIN/MAX over string telemetry) as a string node. Assertions check
the node type (isNumber/isTextual) rather than asText(), since asText()
coerces both node kinds identically - which is why the bug went unnoticed.
- EntityAggregationCalculatedFieldTest: add a strict-types read helper
(useStrictDataTypes=true) and a test asserting the stored SUM/AVG telemetry
are numeric JSON nodes end-to-end. Existing asText()-based assertions are
left untouched (they read via the non-strict endpoint, which stringifies
every value and therefore cannot observe the storage type).
EntityAggregationCalculatedFieldState.toResult() serialized every metric
result via ObjectNode.put(name, JacksonUtil.toString(value)), which produces
a JSON *string* node even for numeric aggregation results (SUM/AVG/COUNT/...).
When persisted (in particular with transport.json.type_cast_enabled=false, or
for non-parsable values) the result lands in ts_kv.str_v, so server-side AVG/SUM
aggregation returns no data in widgets/queries while COUNT/MIN/MAX still return.
Serialize with JacksonUtil.valueToTree(...) so numeric results are emitted as
numeric JSON nodes (-> dbl_v/long_v), mirroring
RelatedEntitiesAggregationCalculatedFieldState. Genuine string MIN/MAX results
over string telemetry are preserved as string nodes. The instanceof Number
guard is retained (it only governs rounding).
A device-state attribute save can complete after its tenant was deleted.
The post-save WS-update callback then resolves an evicted tenant profile
via partitionService.resolve -> getRoutingInfo and throws
TenantNotFoundException uncaught on the ws-callback thread.
Guard resolve() in forwardToSubscriptionManagerService: a deleted tenant
has no partition to route to and no subscribers to notify, so skip the
forward instead of propagating. Mirrors the existing tenant-gone handling
in DefaultDeviceStateService.checkStates().
The deprecation cleanup set @EqualsAndHashCode(callSuper = true) on AlarmComment,
so equals/hashCode now include the BaseData id and createdTime. The delete tests
built the expected comment via a fresh builder (no id), which no longer matches the
server-generated deletion comment (which reuses the original comment id).
Mutate the original alarm comment (already carrying the correct id/createdTime) into
the expected SYSTEM deletion comment instead of building a fresh one.
* feat: send pseudonymized install report (tenant/user hash, tb version) to IoT Hub
* refactor: source install-report tb version/edition from ProjectInfo
Use the existing ProjectInfo component (getProjectVersion/getProductType)
instead of an ad-hoc optional BuildProperties field and a hard-coded "CE"
edition, so PE reports its own product type and the version is the canonical
cleaned value. Constructor-injected via @RequiredArgsConstructor.