CI: feature/editor-account-switch #22
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
import "./.next/dev/types/routes.d.ts";
|
import "./.next/types/routes.d.ts";
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
@@ -15,14 +15,22 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Params {
|
interface Params {
|
||||||
params: {
|
params: Promise<{
|
||||||
id: string;
|
id: string;
|
||||||
};
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function GET(request: Request, ctx: Params) {
|
export async function GET(request: Request, ctx: Params) {
|
||||||
// Next.js 가 params 를 항상 보장하지 않는 상황을 대비해, URL 경로에서 id 를 보완적으로 파싱한다.
|
// Next.js 가 params 를 항상 보장하지 않는 상황을 대비해, URL 경로에서 id 를 보완적으로 파싱한다.
|
||||||
let id: string | undefined = ctx?.params?.id;
|
let id: string | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resolved = await ctx.params;
|
||||||
|
id = resolved?.id;
|
||||||
|
} catch {
|
||||||
|
id = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
try {
|
try {
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { CSSProperties, ReactNode } from "react";
|
import type { CSSProperties, ReactNode } from "react";
|
||||||
import { Fragment, useEffect, useState } from "react";
|
import { Fragment, Suspense, useEffect, useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { FilePlus2, Trash2, Eye, Pencil, ListChecks, Undo2, Redo2 } from "lucide-react";
|
import { FilePlus2, Trash2, Eye, Pencil, ListChecks, Undo2, Redo2 } from "lucide-react";
|
||||||
@@ -71,6 +71,14 @@ import { PropertiesSidebar } from "./panels/PropertiesSidebar";
|
|||||||
import { EditorCanvas } from "./EditorCanvas";
|
import { EditorCanvas } from "./EditorCanvas";
|
||||||
|
|
||||||
export default function EditorPage() {
|
export default function EditorPage() {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<EditorPageInner />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditorPageInner() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const blocks = useEditorStore((state) => state.blocks);
|
const blocks = useEditorStore((state) => state.blocks);
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import "dotenv/config";
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
import { promises as fs } from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const BASE_URL = "http://localhost";
|
||||||
|
|
||||||
|
// /api/video/:id 서브 라우트 TDD
|
||||||
|
// - 존재하는 업로드 파일 id 로 GET /api/video/:id 를 호출하면 200 과 비디오 바이트를 반환해야 한다.
|
||||||
|
// - 존재하지 않는 id 로 호출하면 404 를 반환해야 한다.
|
||||||
|
|
||||||
|
// Prisma 엔진이 실제로 로드되지 않도록 PrismaClient 를 단순 목으로 대체한다.
|
||||||
|
vi.mock("@prisma/client", () => {
|
||||||
|
class PrismaClient {
|
||||||
|
asset = {
|
||||||
|
// GET /api/video/:id 에서는 MIME 타입 조회에만 사용되므로, 테스트에서는 null 을 반환한다.
|
||||||
|
findUnique: async () => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { PrismaClient };
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("/api/video/:id 서브 라우트", () => {
|
||||||
|
const uploadsDir = path.join(process.cwd(), "uploads");
|
||||||
|
let testId: string;
|
||||||
|
let filePath: string;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await fs.mkdir(uploadsDir, { recursive: true });
|
||||||
|
testId = `test-video-${Date.now()}`;
|
||||||
|
filePath = path.join(uploadsDir, testId);
|
||||||
|
await fs.writeFile(filePath, Buffer.from("dummy-video-bytes"));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
try {
|
||||||
|
await fs.unlink(filePath);
|
||||||
|
} catch {
|
||||||
|
// 이미 삭제된 경우는 무시한다.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("업로드된 비디오 파일이 존재하면 200 과 비디오 바이트를 반환해야 한다", async () => {
|
||||||
|
const { GET: handleGet } = await import("@/app/api/video/[id]/route");
|
||||||
|
|
||||||
|
const requestUrl = `${BASE_URL}/api/video/${testId}`;
|
||||||
|
const res = await handleGet(
|
||||||
|
new Request(requestUrl, {
|
||||||
|
headers: {
|
||||||
|
// Referer 기반 핫링크 방지 로직이 있으므로, 동일 호스트의 referer 를 포함한다.
|
||||||
|
referer: `${BASE_URL}/editor`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
// Next 16 타입 정의에서는 context.params 가 Promise 형태이므로, Promise 로 감싼다.
|
||||||
|
{ params: Promise.resolve({ id: testId }) } as any,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get("Content-Type")).toBe("video/mp4");
|
||||||
|
|
||||||
|
const arrayBuffer = await res.arrayBuffer();
|
||||||
|
const buf = Buffer.from(arrayBuffer);
|
||||||
|
expect(buf.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("존재하지 않는 비디오 id 로 요청하면 404 를 반환해야 한다", async () => {
|
||||||
|
const { GET: handleGet } = await import("@/app/api/video/[id]/route");
|
||||||
|
|
||||||
|
const missingId = `missing-${Date.now()}`;
|
||||||
|
const res = await handleGet(
|
||||||
|
new Request(`${BASE_URL}/api/video/${missingId}`, {
|
||||||
|
headers: {
|
||||||
|
referer: `${BASE_URL}/editor`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{ params: Promise.resolve({ id: missingId }) } as any,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res.status).toBe(404);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dummy-video
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dummy-video
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dummy-image
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dummy-image
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dummy-image
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dummy-image
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dummy-section-video
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dummy-section-video
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dummy-video
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dummy-video
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dummy-poster
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dummy-poster
|
||||||
Reference in New Issue
Block a user