flnxi-Wu/license-server/lib/__tests__/registry-auth-route.test.ts
2026-06-02 19:25:48 +08:00

284 lines
9.4 KiB
TypeScript

import { NextRequest } from 'next/server';
jest.mock('@/lib/project-auth', () => ({
getPublicProjectDb: jest.fn(),
}));
jest.mock('@/lib/module-pull-token', () => ({
MODULE_PULL_TOKEN_USERNAME: 'flnxi-module',
hashModulePullToken: jest.fn(() => 'token-hash'),
verifyModulePullToken: jest.fn(),
}));
jest.mock('@/lib/module-grants', () => ({
getAuthorizedModuleGrant: jest.fn(),
}));
import { getPublicProjectDb } from '@/lib/project-auth';
import { verifyModulePullToken } from '@/lib/module-pull-token';
import { getAuthorizedModuleGrant } from '@/lib/module-grants';
const mockGetPublicProjectDb = getPublicProjectDb as jest.MockedFunction<typeof getPublicProjectDb>;
const mockVerifyModulePullToken = verifyModulePullToken as jest.MockedFunction<typeof verifyModulePullToken>;
const mockGetAuthorizedModuleGrant = getAuthorizedModuleGrant as jest.MockedFunction<typeof getAuthorizedModuleGrant>;
let projectPrisma: any;
function createBasicTokenHeader(token: string): string {
return `Basic ${Buffer.from(`flnxi-module:${token}`, 'utf8').toString('base64')}`;
}
describe('registry auth route', () => {
beforeEach(() => {
jest.clearAllMocks();
mockVerifyModulePullToken.mockReturnValue({
tokenType: 'flnxi-module-pull',
projectCode: 'flnxi',
licenseId: 'license-1',
activationId: 'activation-1',
instanceId: 'instance-1',
moduleId: 'intranet-penetration-core',
registryHost: 'registry.test',
repository: 'flnxi/premium/intranet-penetration-core',
tag: '1.0.0',
digest: 'sha256:abc',
iat: 1780272000,
exp: 1780272900,
jti: 'jti-1',
});
mockGetAuthorizedModuleGrant.mockResolvedValue({
moduleId: 'intranet-penetration-core',
artifact: {
type: 'registry-image',
image: 'registry.test/flnxi/premium/intranet-penetration-core:1.0.0',
version: '1.0.0',
digest: 'sha256:abc',
},
});
projectPrisma = {
license: {
findUnique: jest.fn().mockResolvedValue({
id: 'license-1',
licenseType: 'PRO',
status: 'active',
expiresAt: null,
}),
},
activation: {
findFirst: jest.fn().mockResolvedValue({
id: 'activation-1',
licenseId: 'license-1',
instanceId: 'instance-1',
isActive: true,
license: {
id: 'license-1',
licenseType: 'PRO',
status: 'active',
expiresAt: null,
},
}),
},
licenseModuleGrant: {
findUnique: jest.fn(),
findMany: jest.fn(),
create: jest.fn(),
},
modulePullTokenAudit: {
findFirst: jest.fn().mockResolvedValue({
id: 'audit-1',
licenseId: 'license-1',
activationId: 'activation-1',
moduleId: 'intranet-penetration-core',
repository: 'flnxi/premium/intranet-penetration-core',
digest: 'sha256:abc',
expiresAt: new Date('2026-06-02T00:15:00.000Z'),
lastValidatedAt: null,
}),
updateMany: jest.fn(),
},
};
mockGetPublicProjectDb.mockResolvedValue({
success: true,
prisma: projectPrisma,
} as any);
});
it('应允许 token 声明仓库和 digest 完全匹配的拉取请求', async () => {
const { GET } = await import('../../app/api/registry/auth/route');
const request = new NextRequest(
'http://license.test/api/registry/auth?scope=repository:flnxi/premium/intranet-penetration-core:pull&digest=sha256:abc&referenceType=manifests&reference=sha256:abc',
{
headers: {
Authorization: createBasicTokenHeader('pull-token'),
'X-Registry-Host': 'registry.test',
},
}
);
const response = await GET(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data).toEqual(
expect.objectContaining({
success: true,
moduleId: 'intranet-penetration-core',
repository: 'flnxi/premium/intranet-penetration-core',
})
);
expect(mockGetPublicProjectDb).toHaveBeenCalledWith('flnxi');
});
it('manifest tag 和 token 不匹配时应拒绝', async () => {
const { GET } = await import('../../app/api/registry/auth/route');
const request = new NextRequest(
'http://license.test/api/registry/auth?scope=repository:flnxi/premium/intranet-penetration-core:pull&referenceType=manifests&reference=2.0.0',
{
headers: {
Authorization: createBasicTokenHeader('pull-token'),
'X-Registry-Host': 'registry.test',
},
}
);
const response = await GET(request);
expect(response.status).toBe(403);
expect(mockGetPublicProjectDb).not.toHaveBeenCalled();
});
it('manifest tag 和 token 匹配时应允许继续校验项目状态', async () => {
const { GET } = await import('../../app/api/registry/auth/route');
const request = new NextRequest(
'http://license.test/api/registry/auth?scope=repository:flnxi/premium/intranet-penetration-core:pull&referenceType=manifests&reference=1.0.0',
{
headers: {
Authorization: createBasicTokenHeader('pull-token'),
'X-Registry-Host': 'registry.test',
},
}
);
const response = await GET(request);
expect(response.status).toBe(200);
expect(mockGetPublicProjectDb).toHaveBeenCalledWith('flnxi');
});
it('已登记 token 的 blob 层文件请求不应拿镜像 digest 误判', async () => {
const { GET } = await import('../../app/api/registry/auth/route');
const request = new NextRequest(
'http://license.test/api/registry/auth?scope=repository:flnxi/premium/intranet-penetration-core:pull&digest=sha256:layer&referenceType=blobs',
{
headers: {
Authorization: createBasicTokenHeader('pull-token'),
'X-Registry-Host': 'registry.test',
},
}
);
const response = await GET(request);
expect(response.status).toBe(200);
});
it('同一 token 已验证主清单后,应允许 Docker 继续请求平台子清单 digest', async () => {
projectPrisma.modulePullTokenAudit.findFirst.mockResolvedValue({
id: 'audit-1',
licenseId: 'license-1',
activationId: 'activation-1',
moduleId: 'intranet-penetration-core',
repository: 'flnxi/premium/intranet-penetration-core',
digest: 'sha256:abc',
expiresAt: new Date('2026-06-02T00:15:00.000Z'),
lastValidatedAt: new Date(),
});
const { GET } = await import('../../app/api/registry/auth/route');
const request = new NextRequest(
'http://license.test/api/registry/auth?scope=repository:flnxi/premium/intranet-penetration-core:pull&digest=sha256:child-manifest&referenceType=manifests&reference=sha256:child-manifest',
{
headers: {
Authorization: createBasicTokenHeader('pull-token'),
'X-Registry-Host': 'registry.test',
},
}
);
const response = await GET(request);
expect(response.status).toBe(200);
});
it('未先验证主清单时,陌生 manifest digest 仍应拒绝', async () => {
const { GET } = await import('../../app/api/registry/auth/route');
const request = new NextRequest(
'http://license.test/api/registry/auth?scope=repository:flnxi/premium/intranet-penetration-core:pull&digest=sha256:other&referenceType=manifests&reference=sha256:other',
{
headers: {
Authorization: createBasicTokenHeader('pull-token'),
'X-Registry-Host': 'registry.test',
},
}
);
const response = await GET(request);
expect(response.status).toBe(403);
});
it('token 未登记或已过期时应拒绝拉取', async () => {
projectPrisma.modulePullTokenAudit.findFirst.mockResolvedValue(null);
const { GET } = await import('../../app/api/registry/auth/route');
const request = new NextRequest(
'http://license.test/api/registry/auth?scope=repository:flnxi/premium/intranet-penetration-core:pull',
{
headers: {
Authorization: createBasicTokenHeader('pull-token'),
'X-Registry-Host': 'registry.test',
},
}
);
const response = await GET(request);
expect(response.status).toBe(403);
});
it('激活记录失效时应拒绝拉取', async () => {
projectPrisma.activation.findFirst.mockResolvedValue(null);
const { GET } = await import('../../app/api/registry/auth/route');
const request = new NextRequest(
'http://license.test/api/registry/auth?scope=repository:flnxi/premium/intranet-penetration-core:pull',
{
headers: {
Authorization: createBasicTokenHeader('pull-token'),
'X-Registry-Host': 'registry.test',
},
}
);
const response = await GET(request);
expect(response.status).toBe(403);
});
it('请求仓库和 token 不匹配时应拒绝,不进入项目数据库', async () => {
const { GET } = await import('../../app/api/registry/auth/route');
const request = new NextRequest(
'http://license.test/api/registry/auth?scope=repository:flnxi/premium/custom-templates-core:pull',
{
headers: {
Authorization: createBasicTokenHeader('pull-token'),
},
}
);
const response = await GET(request);
expect(response.status).toBe(403);
expect(mockGetPublicProjectDb).not.toHaveBeenCalled();
});
});