2026-07-17 13:50:30 +08:00
|
|
|
import assert from 'node:assert/strict'
|
|
|
|
|
import test from 'node:test'
|
|
|
|
|
import {
|
|
|
|
|
ReleaseSetCompatibilityError,
|
|
|
|
|
planReleaseSet,
|
|
|
|
|
type InstalledPluginModule,
|
|
|
|
|
type PluginReleaseCandidate,
|
|
|
|
|
} from '../src/releaseSet'
|
|
|
|
|
|
|
|
|
|
const installed: InstalledPluginModule[] = [
|
|
|
|
|
{
|
|
|
|
|
moduleId: 'common',
|
|
|
|
|
type: 'common',
|
|
|
|
|
version: '1.0.0',
|
|
|
|
|
appVersionRange: '>=8.0.0 <9.0.0',
|
|
|
|
|
minNativeApiLevel: 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
moduleId: 'app',
|
|
|
|
|
type: 'app',
|
|
|
|
|
version: '1.0.0',
|
|
|
|
|
appVersionRange: '>=8.0.0 <9.0.0',
|
|
|
|
|
commonVersionRange: '>=1.0.0 <2.0.0',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
moduleId: 'buz2',
|
|
|
|
|
type: 'buz',
|
|
|
|
|
version: '1.0.0',
|
|
|
|
|
appVersionRange: '>=8.0.0 <9.0.0',
|
|
|
|
|
commonVersionRange: '>=1.0.0 <2.0.0',
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
function candidate(
|
|
|
|
|
module: Omit<PluginReleaseCandidate, 'downloadUrl' | 'sha256'>,
|
|
|
|
|
): PluginReleaseCandidate {
|
|
|
|
|
return {
|
|
|
|
|
appVersionRange: '>=8.0.0 <9.0.0',
|
|
|
|
|
...module,
|
|
|
|
|
downloadUrl: `https://updates.example/${module.moduleId}.bundle`,
|
|
|
|
|
sha256: 'a'.repeat(64),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test('orders common before app and buz in one cold-start release set', () => {
|
|
|
|
|
const plan = planReleaseSet({
|
|
|
|
|
installed,
|
|
|
|
|
appVersion: '8.0.0',
|
|
|
|
|
nativeApiLevel: 3,
|
|
|
|
|
candidates: [
|
|
|
|
|
candidate({
|
|
|
|
|
moduleId: 'buz1',
|
|
|
|
|
type: 'buz',
|
|
|
|
|
version: '1.0.2',
|
|
|
|
|
commonVersionRange: '>=1.0.1 <2.0.0',
|
|
|
|
|
}),
|
|
|
|
|
candidate({ moduleId: 'common', type: 'common', version: '1.0.2' }),
|
|
|
|
|
candidate({
|
|
|
|
|
moduleId: 'app',
|
|
|
|
|
type: 'app',
|
|
|
|
|
version: '1.0.1',
|
|
|
|
|
commonVersionRange: '>=1.0.1 <2.0.0',
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
plan?.modules.map(module => module.moduleId),
|
|
|
|
|
['common', 'app', 'buz1'],
|
|
|
|
|
)
|
|
|
|
|
assert.equal(plan?.commonVersion, '1.0.2')
|
|
|
|
|
assert.equal(plan?.requiresColdStart, true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('keeps an older buz compatible when common advances in the same major range', () => {
|
|
|
|
|
const plan = planReleaseSet({
|
|
|
|
|
installed,
|
|
|
|
|
appVersion: '8.0.0',
|
|
|
|
|
nativeApiLevel: 1,
|
|
|
|
|
candidates: [candidate({ moduleId: 'common', type: 'common', version: '1.0.2' })],
|
|
|
|
|
})
|
|
|
|
|
assert.equal(plan?.modules.length, 1)
|
|
|
|
|
assert.equal(plan?.commonVersion, '1.0.2')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('rejects a common upgrade that breaks an installed plugin', () => {
|
|
|
|
|
assert.throws(
|
|
|
|
|
() =>
|
|
|
|
|
planReleaseSet({
|
|
|
|
|
installed,
|
|
|
|
|
appVersion: '8.0.0',
|
|
|
|
|
nativeApiLevel: 1,
|
|
|
|
|
candidates: [candidate({ moduleId: 'common', type: 'common', version: '2.0.0' })],
|
|
|
|
|
}),
|
|
|
|
|
(error: unknown) =>
|
|
|
|
|
error instanceof ReleaseSetCompatibilityError &&
|
|
|
|
|
error.moduleId === 'app' &&
|
|
|
|
|
error.reason === 'COMMON_INCOMPATIBLE',
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('rejects candidates that require a newer native API', () => {
|
|
|
|
|
assert.throws(
|
|
|
|
|
() =>
|
|
|
|
|
planReleaseSet({
|
|
|
|
|
installed,
|
|
|
|
|
appVersion: '8.0.0',
|
|
|
|
|
nativeApiLevel: 1,
|
|
|
|
|
candidates: [
|
|
|
|
|
candidate({
|
|
|
|
|
moduleId: 'buz1',
|
|
|
|
|
type: 'buz',
|
|
|
|
|
version: '1.0.1',
|
|
|
|
|
commonVersionRange: '^1.0.0',
|
|
|
|
|
minNativeApiLevel: 2,
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
}),
|
|
|
|
|
(error: unknown) =>
|
|
|
|
|
error instanceof ReleaseSetCompatibilityError && error.reason === 'NATIVE_API_INCOMPATIBLE',
|
2026-07-20 19:31:43 +08:00
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('rejects a plugin built against a different native baseline', () => {
|
|
|
|
|
const nativeBaselineId = 'android-sha256:current'
|
|
|
|
|
const installedForBaseline = installed.map(module => ({
|
|
|
|
|
...module,
|
|
|
|
|
builtAgainstNativeBaselineId: nativeBaselineId,
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
assert.throws(
|
|
|
|
|
() =>
|
|
|
|
|
planReleaseSet({
|
|
|
|
|
installed: installedForBaseline,
|
|
|
|
|
appVersion: '8.0.0',
|
|
|
|
|
nativeApiLevel: 1,
|
|
|
|
|
nativeBaselineId,
|
|
|
|
|
candidates: [
|
|
|
|
|
candidate({
|
|
|
|
|
moduleId: 'common',
|
|
|
|
|
type: 'common',
|
|
|
|
|
version: '1.0.1',
|
|
|
|
|
builtAgainstNativeBaselineId: 'android-sha256:old',
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
}),
|
|
|
|
|
(error: unknown) =>
|
|
|
|
|
error instanceof ReleaseSetCompatibilityError &&
|
|
|
|
|
error.reason === 'NATIVE_BASELINE_INCOMPATIBLE',
|
2026-07-17 13:50:30 +08:00
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('rejects a plugin outside the current full-app version range', () => {
|
|
|
|
|
assert.throws(
|
|
|
|
|
() =>
|
|
|
|
|
planReleaseSet({
|
|
|
|
|
installed,
|
|
|
|
|
appVersion: '8.0.0',
|
|
|
|
|
nativeApiLevel: 1,
|
|
|
|
|
candidates: [
|
|
|
|
|
candidate({
|
|
|
|
|
moduleId: 'buz1',
|
|
|
|
|
type: 'buz',
|
|
|
|
|
version: '1.0.1',
|
|
|
|
|
appVersionRange: '>=8.0.1 <9.0.0',
|
|
|
|
|
commonVersionRange: '^1.0.0',
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
}),
|
|
|
|
|
(error: unknown) =>
|
|
|
|
|
error instanceof ReleaseSetCompatibilityError && error.reason === 'APP_INCOMPATIBLE',
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('ignores equal versions and downgrades', () => {
|
|
|
|
|
const plan = planReleaseSet({
|
|
|
|
|
installed,
|
|
|
|
|
appVersion: '8.0.0',
|
|
|
|
|
nativeApiLevel: 1,
|
|
|
|
|
candidates: [
|
|
|
|
|
candidate({ moduleId: 'common', type: 'common', version: '0.9.0' }),
|
|
|
|
|
candidate({
|
|
|
|
|
moduleId: 'buz2',
|
|
|
|
|
type: 'buz',
|
|
|
|
|
version: '1.0.0',
|
|
|
|
|
commonVersionRange: '^1.0.0',
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
assert.equal(plan, null)
|
|
|
|
|
})
|