chore: test deploy snapshot

This commit is contained in:
2026-05-28 08:09:49 +09:00
parent e195ac8088
commit 983887dc05
30 changed files with 1730 additions and 108 deletions

View File

@@ -144,7 +144,7 @@ export async function registerBaseballTicketBayRoutes(app: FastifyInstance) {
app.delete('/api/baseball-ticket-bay/logs/:id', async (request, reply) => {
const accessContext = await resolveBaseballTicketBayAccessContext(request);
if (!accessContext || accessContext.scope === 'all') {
if (!accessContext) {
return reply.code(400).send({ message: '현재 접근 범위에서는 로그를 삭제할 수 없습니다.' });
}
@@ -197,23 +197,33 @@ export async function registerBaseballTicketBayRoutes(app: FastifyInstance) {
app.patch('/api/baseball-ticket-bay/alerts/:id', async (request, reply) => {
const accessContext = await resolveBaseballTicketBayAccessContext(request);
if (!accessContext || accessContext.scope === 'all') {
if (!accessContext) {
return reply.code(400).send({ message: '현재 접근 범위에서는 알림을 수정할 수 없습니다.' });
}
const params = z.object({ id: z.string().trim().min(1) }).parse(request.params ?? {});
const payload = alertPayloadSchema.partial().parse(request.body ?? {});
const item = await updateBaseballTicketBayAlert(params.id, payload, {
clientId: accessContext.clientId,
ownerType: accessContext.scope === 'shared-token' ? 'shared-token' : 'client',
ownerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.clientId,
appOrigin: readHeader(request, 'x-app-origin'),
appDomain: readHeader(request, 'x-app-domain'),
});
const item = await updateBaseballTicketBayAlert(
params.id,
payload,
accessContext.scope === 'all'
? {
scope: { kind: 'all' },
appOrigin: readHeader(request, 'x-app-origin'),
appDomain: readHeader(request, 'x-app-domain'),
}
: {
clientId: accessContext.clientId,
ownerType: accessContext.scope === 'shared-token' ? 'shared-token' : 'client',
ownerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.clientId,
appOrigin: readHeader(request, 'x-app-origin'),
appDomain: readHeader(request, 'x-app-domain'),
},
);
await createBaseballTicketBayLog({
clientId: accessContext.clientId,
ownerType: accessContext.scope === 'shared-token' ? 'shared-token' : 'client',
ownerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.clientId,
clientId: item.clientId,
ownerType: item.ownerType,
ownerId: item.ownerId,
alertId: item.id,
alertTitle: item.title,
action: payload.active === false ? 'pause' : payload.active === true ? 'resume' : 'run',
@@ -236,7 +246,7 @@ export async function registerBaseballTicketBayRoutes(app: FastifyInstance) {
app.delete('/api/baseball-ticket-bay/alerts/:id', async (request, reply) => {
const accessContext = await resolveBaseballTicketBayAccessContext(request);
if (!accessContext || accessContext.scope === 'all') {
if (!accessContext) {
return reply.code(400).send({ message: '현재 접근 범위에서는 알림을 삭제할 수 없습니다.' });
}
@@ -248,9 +258,9 @@ export async function registerBaseballTicketBayRoutes(app: FastifyInstance) {
}
await createBaseballTicketBayLog({
clientId: accessContext.clientId,
ownerType: accessContext.scope === 'shared-token' ? 'shared-token' : 'client',
ownerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.clientId,
clientId: item.clientId,
ownerType: item.ownerType,
ownerId: item.ownerId,
alertId: item.id,
alertTitle: item.title,
action: 'delete',
@@ -274,10 +284,6 @@ export async function registerBaseballTicketBayRoutes(app: FastifyInstance) {
const params = z.object({ id: z.string().trim().min(1) }).parse(request.params ?? {});
if (accessContext.scope === 'all') {
return reply.code(403).send({ message: '전체 보기 범위에서는 즉시 실행할 수 없습니다.' });
}
const result = await runBaseballTicketBayAlert(params.id, {
ignoreTimeWindow: true,
scope: toOwnerScope(accessContext),

View File

@@ -7,6 +7,7 @@ import { env } from '../config/env.js';
import { registerResourceManagerRoutes, resolveSingleRange } from './resource-manager.js';
const fallbackResourceRoot = path.resolve(process.cwd(), '../../../resource');
const legacyPublicResourceRoot = path.resolve(process.cwd(), '../../../public/resource');
test('resolveSingleRange parses open-ended and suffix byte ranges', () => {
assert.deepEqual(resolveSingleRange('bytes=5-', 20), {
@@ -55,3 +56,61 @@ test('resource manager preview serves 206 partial content for byte ranges', asyn
await app.close();
}
});
test('resource manager preview falls back to public/resource legacy artifacts', async () => {
const app = Fastify();
await registerResourceManagerRoutes(app);
const relativePath = path.join('legacy-preview-test', `sample-${Date.now()}.html`);
const absolutePath = path.join(legacyPublicResourceRoot, relativePath);
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
await fs.writeFile(absolutePath, '<!doctype html><html><body>legacy preview</body></html>', 'utf8');
try {
const response = await app.inject({
method: 'GET',
url: `/api/resource-manager/preview/${relativePath.split(path.sep).map((segment) => encodeURIComponent(segment)).join('/')}?token=${encodeURIComponent(env.SERVER_COMMAND_ACCESS_TOKEN)}`,
});
assert.equal(response.statusCode, 200);
assert.match(String(response.headers['content-type'] ?? ''), /text\/html/i);
assert.match(response.body, /legacy preview/);
} finally {
await fs.rm(path.join(legacyPublicResourceRoot, 'legacy-preview-test'), { recursive: true, force: true });
await app.close();
}
});
test('resource manager preview restores encoded hash fragments in the file name', async () => {
const app = Fastify();
await registerResourceManagerRoutes(app);
const relativePath = path.join('encoded-preview-test', `sample-${Date.now()}.html`);
const absolutePath = path.join(legacyPublicResourceRoot, relativePath);
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
await fs.writeFile(absolutePath, '<!doctype html><html><body>encoded hash preview</body></html>', 'utf8');
try {
for (const encodedSuffix of ['%23option-a', '%2523option-a']) {
const encodedPath = relativePath
.split(path.sep)
.map((segment, index, list) =>
index === list.length - 1
? `${encodeURIComponent(segment)}${encodedSuffix}`
: encodeURIComponent(segment),
)
.join('/');
const response = await app.inject({
method: 'GET',
url: `/api/resource-manager/preview/${encodedPath}?token=${encodeURIComponent(env.SERVER_COMMAND_ACCESS_TOKEN)}`,
});
assert.equal(response.statusCode, 200);
assert.match(String(response.headers['content-type'] ?? ''), /text\/html/i);
assert.match(response.body, /encoded hash preview/);
}
} finally {
await fs.rm(path.join(legacyPublicResourceRoot, 'encoded-preview-test'), { recursive: true, force: true });
await app.close();
}
});