Files
AFFiNE-Mirror/packages/backend/server/src/__tests__/version.spec.ts
T
DarkSky ffc27af3ba fix(server): update version check (#14784)
fix #14780


#### PR Dependency Tree


* **PR #14784** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **Bug Fixes**
* Improved upgrade availability detection to properly compare semantic
versions, including support for prerelease and canary versions. The
system now accurately identifies when new versions are available,
ensuring users receive timely update notifications.

* **Tests**
* Added comprehensive unit tests for version comparison and upgrade
detection functionality.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-05 10:56:05 +08:00

261 lines
6.1 KiB
TypeScript

import { Controller, Get } from '@nestjs/common';
import test from 'ava';
import Sinon from 'sinon';
import { AppModule } from '../app.module';
import {
CANARY_CLIENT_VERSION_MAX_AGE_DAYS,
ConfigFactory,
hasNewerVersion,
UseNamedGuard,
} from '../base';
import { Public } from '../core/auth/guard';
import { VersionService } from '../core/version/service';
import { createTestingApp, TestingApp } from './utils';
@Public()
@Controller('/guarded')
class GuardedController {
@UseNamedGuard('version')
@Get('/test')
test() {
return 'test';
}
}
let app: TestingApp;
let config: ConfigFactory;
let version: VersionService;
function checkVersion(enabled = true) {
config.override({
client: {
versionControl: {
enabled,
requiredVersion: '>=0.25.0',
},
},
});
}
function makeCanaryDateVersion(date: Date, build = '015') {
return `${date.getUTCFullYear()}.${date.getUTCMonth() + 1}.${date.getUTCDate()}-canary.${build}`;
}
test.before(async () => {
app = await createTestingApp({
imports: [AppModule],
controllers: [GuardedController],
});
version = app.get(VersionService, { strict: false });
config = app.get(ConfigFactory, { strict: false });
});
test.beforeEach(async () => {
Sinon.reset();
checkVersion(true);
});
test.after.always(async () => {
await app.close();
});
test('should passthrough if version check is not enabled', async t => {
checkVersion(false);
const spy = Sinon.spy(version, 'checkVersion');
let res = await app.GET('/guarded/test');
t.is(res.status, 200);
res = await app.GET('/guarded/test').set('x-affine-version', '0.20.0');
t.is(res.status, 200);
res = await app.GET('/guarded/test').set('x-affine-version', 'invalid');
t.is(res.status, 200);
t.true(spy.notCalled);
spy.restore();
});
test('should enforce hard required version when version range is invalid', async t => {
config.override({
client: {
versionControl: {
requiredVersion: 'invalid',
},
},
});
let res = await app.GET('/guarded/test').set('x-affine-version', '0.25.0');
t.is(res.status, 200);
res = await app.GET('/guarded/test').set('x-affine-version', 'invalid');
t.is(res.status, 403);
t.is(
res.body.message,
'Unsupported client with version [invalid], required version is [>=0.25.0].'
);
});
test('should pass if client version is allowed', async t => {
let res = await app.GET('/guarded/test').set('x-affine-version', '0.25.0');
t.is(res.status, 200);
res = await app.GET('/guarded/test').set('x-affine-version', '0.26.0');
t.is(res.status, 200);
config.override({
client: {
versionControl: {
requiredVersion: '>=0.25.0',
},
},
});
res = await app.GET('/guarded/test').set('x-affine-version', '0.25.0');
t.is(res.status, 200);
});
test('should fail if client version is not set or invalid', async t => {
let res = await app.GET('/guarded/test');
t.is(res.status, 403);
t.is(
res.body.message,
'Unsupported client with version [unset_or_invalid], required version is [>=0.25.0].'
);
res = await app.GET('/guarded/test').set('x-affine-version', 'invalid');
t.is(res.status, 403);
t.is(
res.body.message,
'Unsupported client with version [invalid], required version is [>=0.25.0].'
);
});
test('should tell upgrade if client version is lower than allowed', async t => {
config.override({
client: {
versionControl: {
requiredVersion: '>=0.26.0 <=0.27.0',
},
},
});
let res = await app.GET('/guarded/test').set('x-affine-version', '0.25.0');
t.is(res.status, 403);
t.is(
res.body.message,
'Unsupported client with version [0.25.0], required version is [>=0.26.0 <=0.27.0].'
);
});
test('should tell downgrade if client version is higher than allowed', async t => {
config.override({
client: {
versionControl: {
requiredVersion: '>=0.25.0 <=0.26.0',
},
},
});
let res = await app.GET('/guarded/test').set('x-affine-version', '0.27.0');
t.is(res.status, 403);
t.is(
res.body.message,
'Unsupported client with version [0.27.0], required version is [>=0.25.0 <=0.26.0].'
);
});
test('should test prerelease version', async t => {
config.override({
client: {
versionControl: {
requiredVersion: '>=0.25.0',
},
},
});
let res = await app
.GET('/guarded/test')
.set('x-affine-version', '0.25.0-canary.1');
// 0.25.0-canary.1 is lower than 0.25.0 obviously
t.is(res.status, 403);
res = await app
.GET('/guarded/test')
.set('x-affine-version', '0.26.0-canary.1');
t.is(res.status, 200);
res = await app.GET('/guarded/test').set('x-affine-version', '0.26.0-beta.2');
t.is(res.status, 200);
});
test('should allow recent canary date version in canary namespace', async t => {
const prevNamespace = env.NAMESPACE;
// @ts-expect-error test
env.NAMESPACE = 'dev';
try {
const res = await app
.GET('/guarded/test')
.set('x-affine-version', makeCanaryDateVersion(new Date(), '015'));
t.is(res.status, 200);
} finally {
// @ts-expect-error test
env.NAMESPACE = prevNamespace;
}
});
test('should reject old canary date version in canary namespace', async t => {
const prevNamespace = env.NAMESPACE;
// @ts-expect-error test
env.NAMESPACE = 'dev';
try {
const old = new Date(
Date.now() -
(CANARY_CLIENT_VERSION_MAX_AGE_DAYS + 1) * 24 * 60 * 60 * 1000
);
const oldVersion = makeCanaryDateVersion(old, '015');
const res = await app
.GET('/guarded/test')
.set('x-affine-version', oldVersion);
t.is(res.status, 403);
t.is(
res.body.message,
`Unsupported client with version [${oldVersion}], required version is [canary (within 2 months)].`
);
} finally {
// @ts-expect-error test
env.NAMESPACE = prevNamespace;
}
});
test('should compare release versions for available upgrades', t => {
t.false(hasNewerVersion('0.26.5', '0.26.4'));
t.false(hasNewerVersion('0.26.5', '0.26.5'));
t.true(hasNewerVersion('0.26.5', '0.26.6'));
t.true(hasNewerVersion('0.26.5', '0.26.6-beta.1'));
t.false(hasNewerVersion('0.26.6-beta.2', '0.26.6-beta.1'));
});