fix(admin): save premium limits and model changes (#3386)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-11 13:26:39 +02:00
1 parent 7d2c51a57f
commit 6acd7f3d22
18 files changed
+1035 -59

No files matched your search

@@ -0,0 +1,73 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {TestAccount} from '@app/api/auth/tests/AuthTestUtils';
import {createTestAccount, setUserACLs} from '@app/api/auth/tests/AuthTestUtils';
import {getInstanceConfigRepository, getLimitConfigService} from '@app/api/middleware/ServiceSingletons';
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
import {HTTP_STATUS} from '@app/api/test/TestConstants';
import {createBuilder} from '@app/api/test/TestRequestBuilder';
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {MAX_BIO_LENGTH} from '@fluxer/constants/src/LimitConstants';
import type {LimitConfigGetResponse} from '@fluxer/schema/src/domains/admin/AdminSchemas';
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
import type {z} from 'zod';
type LimitConfigResponse = z.infer<typeof LimitConfigGetResponse>;
describe('limit config allowed ranges', () => {
let harness: ApiTestHarness;
beforeAll(async () => {
harness = await createApiTestHarness();
});
beforeEach(async () => {
await harness.reset();
});
afterAll(async () => {
await harness.shutdown();
});
async function createAdmin(): Promise<TestAccount> {
return await setUserACLs(harness, await createTestAccount(harness), [
AdminACLs.AUTHENTICATE,
AdminACLs.INSTANCE_LIMIT_CONFIG_VIEW,
AdminACLs.INSTANCE_LIMIT_CONFIG_UPDATE,
]);
}
function vipRule(maxBioLength: number) {
return {id: 'vip', filters: {traits: ['vip']}, limits: {max_bio_length: maxBioLength}};
}
it('refuses a bio length above what a profile can hold', async () => {
const admin = await createAdmin();
await createBuilder(harness, admin.token)
.put('/admin/limit-config')
.body({limit_config: {traitDefinitions: ['vip'], rules: [vipRule(MAX_BIO_LENGTH + 1)]}})
.expect(HTTP_STATUS.BAD_REQUEST, APIErrorCodes.INVALID_FORM_BODY)
.execute();
const saved = await createBuilder<LimitConfigResponse>(harness, admin.token)
.put('/admin/limit-config')
.body({limit_config: {traitDefinitions: ['vip'], rules: [vipRule(MAX_BIO_LENGTH)]}})
.execute();
expect(saved.limit_config.rules.find((rule) => rule.id === 'vip')?.limits).toEqual({
max_bio_length: MAX_BIO_LENGTH,
});
expect(saved.metadata.max_bio_length).toMatchObject({min: 0, max: MAX_BIO_LENGTH});
});
it('reads a stored bio length above the profile maximum as the maximum', async () => {
const admin = await createAdmin();
await getInstanceConfigRepository().setLimitConfig({traitDefinitions: ['vip'], rules: [vipRule(1000)]});
await getLimitConfigService().refreshCache();
const response = await createBuilder<LimitConfigResponse>(harness, admin.token)
.get('/admin/limit-config')
.execute();
expect(response.limit_config.rules.find((rule) => rule.id === 'vip')?.limits).toEqual({
max_bio_length: MAX_BIO_LENGTH,
});
});
});
@@ -0,0 +1,289 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {TestAccount} from '@app/api/auth/tests/AuthTestUtils';
import {createTestAccount, setUserACLs} from '@app/api/auth/tests/AuthTestUtils';
import {getConfig} from '@app/api/Config';
import {getCachedInstancePremiumMode, setCachedInstancePremiumMode} from '@app/api/limits/InstancePremiumModeCache';
import {getInstanceConfigRepository, getLimitConfigService} from '@app/api/middleware/ServiceSingletons';
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder';
import {fetchUserMe, grantPremium} from '@app/api/user/tests/UserTestUtils';
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
import {LIMIT_KEYS} from '@fluxer/constants/src/LimitConfigMetadata';
import {UserPremiumTypes} from '@fluxer/constants/src/UserConstants';
import {DEFAULT_RESTRICTED_LIMITS, DEFAULT_STOCK_LIMITS} from '@fluxer/limits/src/LimitDefaults';
import {expandWireFormat} from '@fluxer/limits/src/LimitDiffer';
import {resolveLimits} from '@fluxer/limits/src/LimitResolver';
import type {LimitConfigSnapshot} from '@fluxer/limits/src/LimitTypes';
import type {LimitConfigGetResponse} from '@fluxer/schema/src/domains/admin/AdminSchemas';
import type {WellKnownFluxerResponse} from '@fluxer/schema/src/domains/instance/InstanceSchemas';
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
import type {z} from 'zod';
type LimitConfigResponse = z.infer<typeof LimitConfigGetResponse>;
const MODE_KEYS = LIMIT_KEYS.filter((key) => DEFAULT_STOCK_LIMITS[key] !== DEFAULT_RESTRICTED_LIMITS[key]);
describe('limit config across premium model switches on a self-hosted instance', () => {
let harness: ApiTestHarness;
let originalSelfHosted: boolean;
let originalPremiumMode: ReturnType<typeof getCachedInstancePremiumMode>;
beforeAll(async () => {
originalSelfHosted = getConfig().instance.selfHosted;
originalPremiumMode = getCachedInstancePremiumMode();
getConfig().instance.selfHosted = true;
harness = await createApiTestHarness();
});
beforeEach(async () => {
await harness.reset();
});
afterAll(async () => {
await harness.shutdown();
getConfig().instance.selfHosted = originalSelfHosted;
setCachedInstancePremiumMode(originalPremiumMode);
});
async function createAdmin(): Promise<TestAccount> {
return await setUserACLs(harness, await createTestAccount(harness), [
AdminACLs.AUTHENTICATE,
AdminACLs.INSTANCE_CONFIG_UPDATE,
AdminACLs.INSTANCE_LIMIT_CONFIG_VIEW,
AdminACLs.INSTANCE_LIMIT_CONFIG_UPDATE,
]);
}
async function setPremiumMode(admin: TestAccount, premiumMode: 'mirror' | 'everyone'): Promise<void> {
await createBuilder(harness, admin.token)
.patch('/admin/instance/config')
.body({policy: {premium_mode: premiumMode}})
.execute();
}
async function getLimitConfig(admin: TestAccount): Promise<LimitConfigResponse> {
return await createBuilder<LimitConfigResponse>(harness, admin.token).get('/admin/limit-config').execute();
}
async function saveRule(admin: TestAccount, ruleId: string, overrides: Record<string, number>): Promise<void> {
const {limit_config: config} = await getLimitConfig(admin);
await createBuilder(harness, admin.token)
.put('/admin/limit-config')
.body({
limit_config: {
traitDefinitions: config.traitDefinitions,
rules: config.rules.map(({modifiedFields: _modifiedFields, ...rule}) =>
rule.id === ruleId ? {...rule, limits: {...rule.limits, ...overrides}} : rule,
),
},
})
.execute();
}
function saveDefaultRule(admin: TestAccount, overrides: Record<string, number>): Promise<void> {
return saveRule(admin, 'default', overrides);
}
function findRule(response: LimitConfigResponse, ruleId: string) {
const rule = response.limit_config.rules.find((candidate) => candidate.id === ruleId);
expect(rule).toBeDefined();
return rule!;
}
function defaultRule(response: LimitConfigResponse) {
return findRule(response, 'default');
}
async function storeLegacyLimitConfig(config: LimitConfigSnapshot): Promise<void> {
await getInstanceConfigRepository().setLimitConfig(config);
await getLimitConfigService().refreshCache();
}
async function changeTag(account: TestAccount, discriminator: string): Promise<number> {
const {response} = await createBuilder(harness, account.token)
.patch('/users/@me')
.body({discriminator, password: account.password})
.executeRaw();
return response.status;
}
it('gives everyone premium limits after a save in mirror mode and keeps edited values', async () => {
const admin = await createAdmin();
await setPremiumMode(admin, 'mirror');
await saveDefaultRule(admin, {max_bio_length: 300});
await setPremiumMode(admin, 'everyone');
const rule = defaultRule(await getLimitConfig(admin));
expect(rule.limits).toEqual({...DEFAULT_STOCK_LIMITS, max_bio_length: 300});
expect(rule.modifiedFields).toEqual(['max_bio_length']);
const discovery = await createBuilderWithoutAuth<WellKnownFluxerResponse>(harness)
.get('/.well-known/fluxer')
.execute();
expect(discovery.limits.rules.find((candidate) => candidate.id === 'default')?.overrides).toMatchObject({
feature_custom_discriminator: 1,
max_guilds: DEFAULT_STOCK_LIMITS.max_guilds,
});
});
it('restores free limits after a save in everyone mode', async () => {
const admin = await createAdmin();
await setPremiumMode(admin, 'everyone');
await saveDefaultRule(admin, {});
await setPremiumMode(admin, 'mirror');
const response = await getLimitConfig(admin);
expect(defaultRule(response).limits).toEqual(DEFAULT_RESTRICTED_LIMITS);
expect(response.limit_config.rules.find((rule) => rule.id === 'premium')?.limits).toEqual(DEFAULT_STOCK_LIMITS);
});
it('stores only the edited values', async () => {
const admin = await createAdmin();
await setPremiumMode(admin, 'mirror');
await saveDefaultRule(admin, {max_bio_length: 300});
const {config: stored} = await getInstanceConfigRepository().readLimitConfigInputs();
expect(stored?.overridesOnly).toBe(true);
expect(stored?.rules).toEqual([
{id: 'premium', filters: {traits: ['premium']}, limits: {}},
{id: 'default', limits: {max_bio_length: 300}},
{id: 'very_large_guild', filters: {guildFeatures: ['VERY_LARGE_GUILD']}, limits: {}},
]);
});
it('repairs free limits stored in full before an earlier switch to everyone', async () => {
const admin = await createAdmin();
await setPremiumMode(admin, 'everyone');
await storeLegacyLimitConfig({
traitDefinitions: ['premium'],
rules: [
{id: 'premium', filters: {traits: ['premium']}, limits: {...DEFAULT_STOCK_LIMITS}},
{
id: 'default',
limits: {...DEFAULT_RESTRICTED_LIMITS, max_bio_length: 300},
modifiedFields: ['max_bio_length'],
},
],
});
const rule = defaultRule(await getLimitConfig(admin));
expect(rule.limits).toEqual({...DEFAULT_STOCK_LIMITS, max_bio_length: 300});
expect(rule.modifiedFields).toEqual(['max_bio_length']);
});
it('repairs premium limits stored in full before an earlier switch to mirror', async () => {
const admin = await createAdmin();
await setPremiumMode(admin, 'mirror');
await storeLegacyLimitConfig({
traitDefinitions: [],
rules: [{id: 'default', limits: {...DEFAULT_STOCK_LIMITS}}],
});
const response = await getLimitConfig(admin);
expect(defaultRule(response).limits).toEqual(DEFAULT_RESTRICTED_LIMITS);
expect(defaultRule(response).modifiedFields).toBeUndefined();
});
it('repairs free limits saved again after an earlier switch to everyone', async () => {
const admin = await createAdmin();
await setPremiumMode(admin, 'everyone');
await storeLegacyLimitConfig({
traitDefinitions: [],
rules: [{id: 'default', limits: {...DEFAULT_RESTRICTED_LIMITS}, modifiedFields: MODE_KEYS}],
});
const rule = defaultRule(await getLimitConfig(admin));
expect(rule.limits).toEqual(DEFAULT_STOCK_LIMITS);
expect(rule.modifiedFields).toBeUndefined();
const member = await createTestAccount(harness);
expect(await changeTag(member, '4242')).toBe(200);
await saveDefaultRule(admin, {max_bio_length: 300});
const {config: stored} = await getInstanceConfigRepository().readLimitConfigInputs();
expect(stored?.rules.find((candidate) => candidate.id === 'default')).toEqual({
id: 'default',
limits: {max_bio_length: 300},
});
});
it('repairs premium limits saved again after an earlier switch to mirror', async () => {
const admin = await createAdmin();
await setPremiumMode(admin, 'mirror');
await storeLegacyLimitConfig({
traitDefinitions: [],
rules: [{id: 'default', limits: {...DEFAULT_STOCK_LIMITS}, modifiedFields: MODE_KEYS}],
});
const response = await getLimitConfig(admin);
expect(defaultRule(response).limits).toEqual(DEFAULT_RESTRICTED_LIMITS);
expect(findRule(response, 'premium').limits).toEqual(DEFAULT_STOCK_LIMITS);
expect(response.limit_config.traitDefinitions).toEqual(['premium']);
});
it('reads limits stored in full under the model that is being left', async () => {
const admin = await createAdmin();
await setPremiumMode(admin, 'mirror');
await storeLegacyLimitConfig({
traitDefinitions: ['premium'],
rules: [
{id: 'premium', filters: {traits: ['premium']}, limits: {...DEFAULT_STOCK_LIMITS}},
{id: 'default', limits: {...DEFAULT_RESTRICTED_LIMITS, max_guilds: 150}},
],
});
await setPremiumMode(admin, 'everyone');
expect(defaultRule(await getLimitConfig(admin)).limits).toEqual({...DEFAULT_STOCK_LIMITS, max_guilds: 150});
const {config: stored} = await getInstanceConfigRepository().readLimitConfigInputs();
expect(stored?.rules).toEqual([
{id: 'premium', filters: {traits: ['premium']}, limits: {}},
{id: 'default', limits: {max_guilds: 150}},
]);
});
it('keeps edits that match the other model across a save there', async () => {
const admin = await createAdmin();
await setPremiumMode(admin, 'mirror');
const edits = {feature_custom_discriminator: 1, max_guilds: DEFAULT_STOCK_LIMITS.max_guilds};
await saveDefaultRule(admin, edits);
await setPremiumMode(admin, 'everyone');
await saveDefaultRule(admin, {max_bio_length: 300});
await setPremiumMode(admin, 'mirror');
const rule = defaultRule(await getLimitConfig(admin));
expect(rule.limits).toEqual({...DEFAULT_RESTRICTED_LIMITS, ...edits, max_bio_length: 300});
expect(rule.modifiedFields).toEqual(['feature_custom_discriminator', 'max_bio_length', 'max_guilds']);
});
it('keeps premium rule edits across a save while everyone hides the rule', async () => {
const admin = await createAdmin();
await setPremiumMode(admin, 'mirror');
const edits = {max_guilds: 999, feature_animated_avatar: 0};
await saveRule(admin, 'premium', edits);
await setPremiumMode(admin, 'everyone');
const hidden = await getLimitConfig(admin);
expect(hidden.limit_config.rules.map((rule) => rule.id)).toEqual(['default', 'very_large_guild']);
expect(hidden.limit_config.traitDefinitions).toEqual([]);
await saveDefaultRule(admin, {max_bio_length: 300});
await setPremiumMode(admin, 'mirror');
const response = await getLimitConfig(admin);
expect(findRule(response, 'premium').limits).toEqual({...DEFAULT_STOCK_LIMITS, ...edits});
expect(response.limit_config.traitDefinitions).toEqual(['premium']);
expect(defaultRule(response).limits).toEqual({...DEFAULT_RESTRICTED_LIMITS, max_bio_length: 300});
});
it('lets only premium members change their tag in mirror mode', async () => {
const admin = await createAdmin();
await setPremiumMode(admin, 'everyone');
await saveDefaultRule(admin, {});
await setPremiumMode(admin, 'mirror');
const free = await createTestAccount(harness);
const subscriber = await createTestAccount(harness);
const gifted = await createTestAccount(harness);
await grantPremium(harness, subscriber.userId, UserPremiumTypes.SUBSCRIPTION);
await grantPremium(harness, gifted.userId, UserPremiumTypes.LIFETIME);
expect(await changeTag(free, '4242')).toBe(400);
expect(await changeTag(subscriber, '4242')).toBe(200);
expect(await changeTag(gifted, '4243')).toBe(200);
const {json: me} = await fetchUserMe(harness, subscriber.token);
expect(me.traits).toContain('premium');
const discovery = await createBuilderWithoutAuth<WellKnownFluxerResponse>(harness)
.get('/.well-known/fluxer')
.execute();
const {limits} = resolveLimits(expandWireFormat(discovery.limits), {
traits: new Set(me.traits),
guildFeatures: new Set(),
});
expect(limits.feature_custom_discriminator).toBe(1);
expect(limits.max_guilds).toBe(DEFAULT_STOCK_LIMITS.max_guilds);
});
});