test: build HMAC tenant fixtures from domain, drop product URLs

HMAC identity tests should not hardcode gholak/montzar hosts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-09 22:12:21 +03:30
co-authored by Cursor
parent 82e74f86f0
commit 38337c62d7
2 changed files with 54 additions and 54 deletions
+52 -51
View File
@@ -1,65 +1,66 @@
import { findTenantForHost, scoreTenantHost } from './tenant-host.util'; import {
findTenantForHost,
scoreTenantHost,
type TenantHostFields,
} from './tenant-host.util';
const gholak = { function fromDomain(
domain: 'app.gholaktala.ir', domain: string,
domains: [], extra: Partial<TenantHostFields> = {},
appUrl: 'https://app.gholaktala.ir', ): TenantHostFields {
adminUrl: 'https://k.montzar.ir/admin', return {
nodeServer: 'https://new.gholaktala.ir/server', domain,
}; domains: [],
appUrl: `https://${domain}`,
const montzar = { adminUrl: null,
domain: 'k.montzar.ir', nodeServer: null,
domains: ['montzar.ir'], ...extra,
appUrl: 'https://k.montzar.ir', };
adminUrl: 'https://k.montzar.ir', }
nodeServer: 'https://new.gholaktala.ir/server',
};
const otherOnSameBox = {
domain: 'examplegold.ir',
domains: [],
appUrl: 'https://examplegold.ir',
adminUrl: null,
nodeServer: 'https://new.gholaktala.ir/server',
};
describe('findTenantForHost', () => { describe('findTenantForHost', () => {
const tenants = [gholak, otherOnSameBox, montzar]; it('hmac matches the tenant whose domain is the host', () => {
const host = 'shop.example.ir';
const mine = fromDomain(host);
const other = fromDomain('other.example.ir', {
adminUrl: `https://${host}/admin`,
});
it('hmac does not treat shared adminUrl as gholaktala', () => { expect(scoreTenantHost(other, host, 'hmac')).toBe(0);
expect(scoreTenantHost(gholak, 'k.montzar.ir', 'hmac')).toBe(0); expect(findTenantForHost([other, mine], host, 'hmac')?.domain).toBe(host);
expect(findTenantForHost(tenants, 'k.montzar.ir', 'hmac')?.domain).toBe(
'k.montzar.ir',
);
}); });
it('hmac without montzar tenant does not fall back to gholak adminUrl', () => { it('hmac does not use another tenant adminUrl as identity', () => {
expect( const host = 'panel.example.ir';
findTenantForHost([gholak, otherOnSameBox], 'k.montzar.ir', 'hmac'), const other = fromDomain('app.other.ir', {
).toBeUndefined(); adminUrl: `https://${host}/admin`,
});
expect(findTenantForHost([other], host, 'hmac')).toBeUndefined();
}); });
it('full get_user still maps k.montzar.ir to montzar by primary domain', () => { it('full match uses primary domain when present', () => {
expect(findTenantForHost(tenants, 'k.montzar.ir')?.domain).toBe( const host = 'shop.example.ir';
'k.montzar.ir', expect(findTenantForHost([fromDomain(host)], host)?.domain).toBe(host);
);
}); });
it('maps new.gholaktala.ir to gholaktala via subdomain, not nodeServer', () => { it('subdomain of the tenant domain beats a shared nodeServer', () => {
const gholakBare = { ...gholak, domain: 'gholaktala.ir' }; const root = 'shop.example.ir';
expect( const api = `new.${root}`;
findTenantForHost([montzar, otherOnSameBox, gholakBare], 'new.gholaktala.ir') const mine = fromDomain(root, { nodeServer: `https://${api}/server` });
?.domain, const roommate = fromDomain('roommate.ir', {
).toBe('gholaktala.ir'); nodeServer: `https://${api}/server`,
expect(scoreTenantHost(gholakBare, 'new.gholaktala.ir')).toBe(75); });
expect(findTenantForHost([roommate, mine], api)?.domain).toBe(root);
expect(scoreTenantHost(mine, api)).toBe(75);
}); });
it('does not pick a random tenant when only nodeServer matches several rows', () => { it('does not pick a tenant when only a shared nodeServer matches', () => {
const sharedOnly = [ const api = 'box.example.ir';
{ ...montzar, domain: 'a.ir', domains: [] }, const left = fromDomain('a.ir', { nodeServer: `https://${api}/server` });
{ ...gholak, domain: 'b.ir', domains: [] }, const right = fromDomain('b.ir', { nodeServer: `https://${api}/server` });
];
expect(findTenantForHost(sharedOnly, 'new.gholaktala.ir')).toBeUndefined(); expect(findTenantForHost([left, right], api)).toBeUndefined();
}); });
}); });
+2 -3
View File
@@ -54,7 +54,7 @@ function hostsEqual(left: string, right: string): boolean {
return left.replace(/^www\./, '') === right.replace(/^www\./, ''); return left.replace(/^www\./, '') === right.replace(/^www\./, '');
} }
/** new.gholaktala.ir belongs to tenant domain gholaktala.ir — not via shared nodeServer. */ /** new.shop.ir belongs to tenant domain shop.ir — not via shared nodeServer. */
function isSubdomainOf(host: string, parent: string): boolean { function isSubdomainOf(host: string, parent: string): boolean {
const child = normalizeTenantHost(host); const child = normalizeTenantHost(host);
const root = normalizeTenantHost(parent).replace(/^www\./, ''); const root = normalizeTenantHost(parent).replace(/^www\./, '');
@@ -66,8 +66,7 @@ export type TenantHostMatchMode = 'full' | 'hmac';
/** /**
* full: get_user — دامنه، alias، app/admin/node. * full: get_user — دامنه، alias، app/admin/node.
* hmac: فقط دامنهٔ اصلی نسخه (و ساب‌دامنهٔ همان). adminUrl مشترک * hmac: فقط دامنهٔ اصلی نسخه (و ساب‌دامنهٔ همان). adminUrl هویت نیست.
* (k.montzar.ir روی نسخهٔ app.gholaktala.ir) هویت نیست.
*/ */
export function scoreTenantHost( export function scoreTenantHost(
tenant: TenantHostFields, tenant: TenantHostFields,