mirror of
https://github.com/vernu/textbee.git
synced 2026-09-03 03:29:58 +03:00
Merge pull request #299 from vernu/fix/os-build-fingerprint
Keep the build string a current client reports
This commit is contained in:
@@ -691,6 +691,59 @@ describe('GatewayService', () => {
|
||||
expect(update.$set.os).toBe('Android')
|
||||
})
|
||||
|
||||
it('persists the build string a current client reports', async () => {
|
||||
// `os` is the plain 'Android' label now, so the build string arrives in
|
||||
// its own field. If the normalizer ignores it the field becomes writable
|
||||
// only by the backfill script and no app build can ever populate it.
|
||||
const fingerprint =
|
||||
'samsung/e3qxxx/e3q:16/BP2A.250605.031.A3/S928BXXU4CYI7:user/release-keys'
|
||||
mockDeviceModel.findById.mockResolvedValue(mockDevice)
|
||||
mockDeviceModel.findByIdAndUpdate.mockResolvedValue(mockDevice)
|
||||
|
||||
await service.updateDevice(mockDeviceId, {
|
||||
...mockDeviceInput,
|
||||
os: 'Android',
|
||||
osVersion: '16',
|
||||
osApiLevel: 36,
|
||||
osBuildFingerprint: fingerprint,
|
||||
} as RegisterDeviceInputDTO)
|
||||
|
||||
const [, update] = mockDeviceModel.findByIdAndUpdate.mock.calls[0]
|
||||
expect(update.$set).toMatchObject({
|
||||
os: 'Android',
|
||||
osVersion: '16',
|
||||
osApiLevel: 36,
|
||||
osBuildFingerprint: fingerprint,
|
||||
})
|
||||
})
|
||||
|
||||
it.each([
|
||||
['osVersion', { osVersion: '' }],
|
||||
['osApiLevel', { osApiLevel: null }],
|
||||
['osBuildFingerprint', { osBuildFingerprint: '' }],
|
||||
])(
|
||||
'must not blank a stored %s sent empty by a non-first-party client',
|
||||
async (field, payload) => {
|
||||
// The gateway API is public and has no ValidationPipe, so these can
|
||||
// arrive from any client, not just the Android app.
|
||||
mockDeviceModel.findById.mockResolvedValue({
|
||||
...mockDevice,
|
||||
osVersion: '14',
|
||||
osApiLevel: 34,
|
||||
osBuildFingerprint: 'samsung/a13nnxx/a13:14/UP1A.231005.007/x:user/release-keys',
|
||||
})
|
||||
mockDeviceModel.findByIdAndUpdate.mockResolvedValue(mockDevice)
|
||||
|
||||
await service.updateDevice(mockDeviceId, {
|
||||
...mockDeviceInput,
|
||||
...payload,
|
||||
} as RegisterDeviceInputDTO)
|
||||
|
||||
const [, update] = mockDeviceModel.findByIdAndUpdate.mock.calls[0]
|
||||
expect(update.$set).not.toHaveProperty(field)
|
||||
},
|
||||
)
|
||||
|
||||
it('should ignore a client-sent isDefault', async () => {
|
||||
// set-default is the only route allowed to move the default flag
|
||||
mockDeviceModel.findById.mockResolvedValue(mockDevice)
|
||||
|
||||
@@ -83,6 +83,43 @@ describe('normalizeOsFields', () => {
|
||||
osApiLevel: 36,
|
||||
})
|
||||
})
|
||||
|
||||
// Current clients put the build string in its own field, because `os` now
|
||||
// carries the plain 'Android' label and so has no '/' to parse. Without
|
||||
// this the field is only ever writable by the backfill script.
|
||||
it('keeps the build string reported in its own field', () => {
|
||||
expect(
|
||||
normalizeOsFields({
|
||||
os: 'Android',
|
||||
osVersion: '16',
|
||||
osApiLevel: 36,
|
||||
osBuildFingerprint: FP_16,
|
||||
}),
|
||||
).toEqual({
|
||||
osVersion: '16',
|
||||
osApiLevel: 36,
|
||||
osBuildFingerprint: FP_16,
|
||||
os: 'Android',
|
||||
})
|
||||
})
|
||||
|
||||
it('prefers the reported build string over one parsed out of os', () => {
|
||||
const patch = normalizeOsFields({ os: FP_14, osBuildFingerprint: FP_16 })
|
||||
expect(patch.osBuildFingerprint).toBe(FP_16)
|
||||
})
|
||||
|
||||
it('emits no build string when the reported one is blank', () => {
|
||||
// BASE_OS is empty on many devices, and '' is not null, so it would
|
||||
// otherwise reach $set.
|
||||
for (const blank of ['', ' ']) {
|
||||
const patch = normalizeOsFields({ os: 'Android', osBuildFingerprint: blank })
|
||||
expect(patch).not.toHaveProperty('osBuildFingerprint')
|
||||
}
|
||||
})
|
||||
|
||||
it('falls back to os when no build string is reported', () => {
|
||||
expect(normalizeOsFields({ os: FP_14 }).osBuildFingerprint).toBe(FP_14)
|
||||
})
|
||||
})
|
||||
|
||||
describe('partial payloads', () => {
|
||||
|
||||
@@ -45,7 +45,15 @@ export function normalizeOsFields(input: OsFieldsInput): Record<string, any> {
|
||||
patch.osApiLevel = input.osApiLevel
|
||||
}
|
||||
|
||||
if (raw && raw.includes('/')) patch.osBuildFingerprint = raw
|
||||
// Current clients send the build string in its own field, since `os` now
|
||||
// carries the plain 'Android' label. Older clients only ever put it in `os`.
|
||||
const reportedFingerprint = input?.osBuildFingerprint?.trim()
|
||||
if (reportedFingerprint) {
|
||||
patch.osBuildFingerprint = reportedFingerprint
|
||||
} else if (raw && raw.includes('/')) {
|
||||
patch.osBuildFingerprint = raw
|
||||
}
|
||||
|
||||
if (input?.os !== undefined) patch.os = 'Android'
|
||||
|
||||
return patch
|
||||
|
||||
Reference in New Issue
Block a user