summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorgrothedev <grothedev@gmail.com>2025-12-28 21:41:07 -0500
committergrothedev <grothedev@gmail.com>2025-12-28 21:41:07 -0500
commitbd6c3a07a82ba11cf7b0423307229891675e7ed3 (patch)
treefce602bc4038f0f79e12f9fb296e3d220915da23 /tests
parentf978ad7db04ced4cbcf04a82bf6f0cc3f4ce66a3 (diff)
phase 1 complete i guessHEADmain
Diffstat (limited to 'tests')
-rw-r--r--tests/fileUtils.test.ts112
-rw-r--r--tests/integration.test.ts270
-rw-r--r--tests/rateLimiter.test.ts106
3 files changed, 488 insertions, 0 deletions
diff --git a/tests/fileUtils.test.ts b/tests/fileUtils.test.ts
new file mode 100644
index 0000000..15bfa3d
--- /dev/null
+++ b/tests/fileUtils.test.ts
@@ -0,0 +1,112 @@
+import { assertEquals, assertExists } from "@std/assert";
+import {
+ getUniqueFilename,
+ getMimeType,
+ isViewableInBrowser,
+ formatFileSize,
+ isAllowedFileType,
+} from "../fileUtils.ts";
+
+Deno.test("getMimeType - returns correct MIME type for images", () => {
+ assertEquals(getMimeType("photo.jpg"), "image/jpeg");
+ assertEquals(getMimeType("image.png"), "image/png");
+ assertEquals(getMimeType("graphic.gif"), "image/gif");
+ assertEquals(getMimeType("pic.webp"), "image/webp");
+});
+
+Deno.test("getMimeType - returns correct MIME type for videos", () => {
+ assertEquals(getMimeType("video.mp4"), "video/mp4");
+ assertEquals(getMimeType("clip.webm"), "video/webm");
+ assertEquals(getMimeType("movie.avi"), "video/x-msvideo");
+});
+
+Deno.test("getMimeType - returns correct MIME type for documents", () => {
+ assertEquals(getMimeType("document.pdf"), "application/pdf");
+ assertEquals(getMimeType("readme.txt"), "text/plain");
+ assertEquals(getMimeType("archive.zip"), "application/zip");
+});
+
+Deno.test("getMimeType - handles uppercase extensions", () => {
+ assertEquals(getMimeType("PHOTO.JPG"), "image/jpeg");
+ assertEquals(getMimeType("Video.MP4"), "video/mp4");
+});
+
+Deno.test("getMimeType - returns octet-stream for unknown types", () => {
+ assertEquals(getMimeType("file.xyz"), "application/octet-stream");
+ assertEquals(getMimeType("noextension"), "application/octet-stream");
+});
+
+Deno.test("isViewableInBrowser - images are viewable", () => {
+ assertEquals(isViewableInBrowser("image/jpeg"), true);
+ assertEquals(isViewableInBrowser("image/png"), true);
+ assertEquals(isViewableInBrowser("image/gif"), true);
+});
+
+Deno.test("isViewableInBrowser - videos are viewable", () => {
+ assertEquals(isViewableInBrowser("video/mp4"), true);
+ assertEquals(isViewableInBrowser("video/webm"), true);
+});
+
+Deno.test("isViewableInBrowser - PDFs and text are viewable", () => {
+ assertEquals(isViewableInBrowser("application/pdf"), true);
+ assertEquals(isViewableInBrowser("text/plain"), true);
+});
+
+Deno.test("isViewableInBrowser - archives are not viewable", () => {
+ assertEquals(isViewableInBrowser("application/zip"), false);
+ assertEquals(isViewableInBrowser("application/octet-stream"), false);
+});
+
+Deno.test("formatFileSize - formats bytes correctly", () => {
+ assertEquals(formatFileSize(0), "0 Bytes");
+ assertEquals(formatFileSize(500), "500 Bytes");
+ assertEquals(formatFileSize(1024), "1 KB");
+ assertEquals(formatFileSize(1536), "1.5 KB");
+ assertEquals(formatFileSize(1048576), "1 MB");
+ assertEquals(formatFileSize(1073741824), "1 GB");
+});
+
+Deno.test("isAllowedFileType - allows configured types", () => {
+ assertEquals(isAllowedFileType("image/jpeg"), true);
+ assertEquals(isAllowedFileType("application/pdf"), true);
+});
+
+Deno.test("getUniqueFilename - returns same name if file doesn't exist", async () => {
+ const testDir = await Deno.makeTempDir();
+
+ try {
+ // Save original config
+ const { config } = await import("../config.ts");
+ const originalUploadDir = config.uploadDir;
+ config.uploadDir = testDir;
+
+ const filename = await getUniqueFilename("test.txt");
+ assertEquals(filename, "test.txt");
+
+ // Restore
+ config.uploadDir = originalUploadDir;
+ } finally {
+ await Deno.remove(testDir, { recursive: true });
+ }
+});
+
+Deno.test("getUniqueFilename - appends epoch if file exists", async () => {
+ const testDir = await Deno.makeTempDir();
+
+ try {
+ // Create a test file
+ await Deno.writeTextFile(`${testDir}/existing.txt`, "test");
+
+ const { config } = await import("../config.ts");
+ const originalUploadDir = config.uploadDir;
+ config.uploadDir = testDir;
+
+ const filename = await getUniqueFilename("existing.txt");
+ assertEquals(filename.startsWith("existing_"), true);
+ assertEquals(filename.endsWith(".txt"), true);
+
+ config.uploadDir = originalUploadDir;
+ } finally {
+ await Deno.remove(testDir, { recursive: true });
+ }
+});
diff --git a/tests/integration.test.ts b/tests/integration.test.ts
new file mode 100644
index 0000000..666115a
--- /dev/null
+++ b/tests/integration.test.ts
@@ -0,0 +1,270 @@
+import { assertEquals, assertExists } from "@std/assert";
+
+const BASE_URL = "http://localhost:8000";
+
+// Helper to check if server is running
+async function isServerRunning(): Promise<boolean> {
+ try {
+ const response = await fetch(BASE_URL);
+ // Consume the response body to prevent resource leak
+ await response.body?.cancel();
+ return response.ok;
+ } catch {
+ return false;
+ }
+}
+
+Deno.test({
+ name: "GET / - returns upload page",
+ async fn() {
+ if (!await isServerRunning()) {
+ console.log("⚠️ Server not running, skipping integration test");
+ return;
+ }
+
+ const response = await fetch(BASE_URL);
+ assertEquals(response.status, 200);
+ assertEquals(response.headers.get("content-type"), "text/html");
+
+ const html = await response.text();
+ assertExists(html.includes("File Upload"));
+ },
+});
+
+Deno.test({
+ name: "GET /browse - returns browse page",
+ async fn() {
+ if (!await isServerRunning()) {
+ console.log("⚠️ Server not running, skipping integration test");
+ return;
+ }
+
+ const response = await fetch(`${BASE_URL}/browse`);
+ assertEquals(response.status, 200);
+ assertEquals(response.headers.get("content-type"), "text/html");
+
+ const html = await response.text();
+ assertExists(html.includes("Browse Files"));
+ },
+});
+
+Deno.test({
+ name: "GET /gallery - returns gallery page",
+ async fn() {
+ if (!await isServerRunning()) {
+ console.log("⚠️ Server not running, skipping integration test");
+ return;
+ }
+
+ const response = await fetch(`${BASE_URL}/gallery`);
+ assertEquals(response.status, 200);
+ assertEquals(response.headers.get("content-type"), "text/html");
+
+ const html = await response.text();
+ assertExists(html.includes("Gallery"));
+ },
+});
+
+Deno.test({
+ name: "POST /upload - uploads a file",
+ async fn() {
+ if (!await isServerRunning()) {
+ console.log("⚠️ Server not running, skipping integration test");
+ return;
+ }
+
+ const formData = new FormData();
+ const testFile = new File(["test content"], "test.txt", { type: "text/plain" });
+ formData.append("files", testFile);
+
+ const response = await fetch(`${BASE_URL}/upload`, {
+ method: "POST",
+ body: formData,
+ });
+
+ if (response.status === 429) {
+ console.log("⚠️ Rate limit exceeded, skipping test");
+ await response.body?.cancel();
+ return;
+ }
+
+ assertEquals(response.status, 200);
+ const data = await response.json();
+ assertExists(data.files);
+ assertEquals(Array.isArray(data.files), true);
+ assertEquals(data.files.length, 1);
+ },
+});
+
+Deno.test({
+ name: "POST /upload - rejects file exceeding size limit",
+ async fn() {
+ if (!await isServerRunning()) {
+ console.log("⚠️ Server not running, skipping integration test");
+ return;
+ }
+
+ // Create a file larger than the configured max size (512MB)
+ // For testing, we'll just check if the validation logic works
+ const formData = new FormData();
+
+ // Create a large blob (10MB for testing purposes)
+ const largeContent = new Uint8Array(10 * 1024 * 1024);
+ const largeFile = new File([largeContent], "large.bin", { type: "application/octet-stream" });
+ formData.append("files", largeFile);
+
+ const response = await fetch(`${BASE_URL}/upload`, {
+ method: "POST",
+ body: formData,
+ });
+
+ if (response.status === 429) {
+ console.log("⚠️ Rate limit exceeded, skipping test");
+ await response.body?.cancel();
+ return;
+ }
+
+ // Should succeed because 10MB < 512MB limit
+ assertEquals(response.status, 200);
+ await response.body?.cancel();
+ },
+});
+
+Deno.test({
+ name: "GET /download/:filename - downloads uploaded file",
+ async fn() {
+ if (!await isServerRunning()) {
+ console.log("⚠️ Server not running, skipping integration test");
+ return;
+ }
+
+ // First upload a file
+ const formData = new FormData();
+ const testContent = "download test content";
+ const testFile = new File([testContent], "downloadtest.txt", { type: "text/plain" });
+ formData.append("files", testFile);
+
+ const uploadResponse = await fetch(`${BASE_URL}/upload`, {
+ method: "POST",
+ body: formData,
+ });
+
+ if (uploadResponse.status === 429) {
+ console.log("⚠️ Rate limit exceeded, skipping test");
+ await uploadResponse.body?.cancel();
+ return;
+ }
+
+ const uploadData = await uploadResponse.json();
+ const filename = uploadData.files[0];
+
+ // Now download it
+ const downloadResponse = await fetch(`${BASE_URL}/download/${encodeURIComponent(filename)}`);
+ assertEquals(downloadResponse.status, 200);
+
+ const content = await downloadResponse.text();
+ assertEquals(content, testContent);
+ },
+});
+
+Deno.test({
+ name: "GET /download/:filename - handles spaces in filename",
+ async fn() {
+ if (!await isServerRunning()) {
+ console.log("⚠️ Server not running, skipping integration test");
+ return;
+ }
+
+ // Upload a file with spaces
+ const formData = new FormData();
+ const testContent = "space test content";
+ const testFile = new File([testContent], "test file with spaces.txt", { type: "text/plain" });
+ formData.append("files", testFile);
+
+ const uploadResponse = await fetch(`${BASE_URL}/upload`, {
+ method: "POST",
+ body: formData,
+ });
+
+ if (uploadResponse.status === 429) {
+ console.log("⚠️ Rate limit exceeded, skipping test");
+ await uploadResponse.body?.cancel();
+ return;
+ }
+
+ const uploadData = await uploadResponse.json();
+ const filename = uploadData.files[0];
+
+ // Download with encoded filename
+ const downloadResponse = await fetch(`${BASE_URL}/download/${encodeURIComponent(filename)}`);
+ assertEquals(downloadResponse.status, 200);
+
+ const content = await downloadResponse.text();
+ assertEquals(content, testContent);
+ },
+});
+
+Deno.test({
+ name: "GET /download/nonexistent - returns 404",
+ async fn() {
+ if (!await isServerRunning()) {
+ console.log("⚠️ Server not running, skipping integration test");
+ return;
+ }
+
+ const response = await fetch(`${BASE_URL}/download/nonexistent.txt`);
+ assertEquals(response.status, 404);
+ // Consume response body to prevent leak
+ await response.body?.cancel();
+ },
+});
+
+Deno.test({
+ name: "POST /upload-chunk - uploads file in chunks",
+ async fn() {
+ if (!await isServerRunning()) {
+ console.log("⚠️ Server not running, skipping integration test");
+ return;
+ }
+
+ const content = "chunked upload test content";
+ const filename = "chunked-test.txt";
+ const blob = new Blob([content]);
+ const chunkSize = 10; // Small chunk size for testing
+ const chunks = Math.ceil(blob.size / chunkSize);
+
+ // Upload chunks
+ for (let i = 0; i < chunks; i++) {
+ const start = i * chunkSize;
+ const end = Math.min(start + chunkSize, blob.size);
+ const chunk = blob.slice(start, end);
+
+ const formData = new FormData();
+ formData.append("chunk", new File([chunk], "chunk"));
+ formData.append("filename", filename);
+ formData.append("chunkIndex", String(i));
+ formData.append("totalChunks", String(chunks));
+
+ const response = await fetch(`${BASE_URL}/upload-chunk`, {
+ method: "POST",
+ body: formData,
+ });
+
+ if (response.status === 429) {
+ console.log("⚠️ Rate limit exceeded, skipping test");
+ await response.body?.cancel();
+ return;
+ }
+
+ assertEquals(response.status, 200);
+ await response.body?.cancel();
+ }
+
+ // Verify the file was assembled correctly
+ const downloadResponse = await fetch(`${BASE_URL}/download/${encodeURIComponent(filename)}`);
+ assertEquals(downloadResponse.status, 200);
+
+ const downloadedContent = await downloadResponse.text();
+ assertEquals(downloadedContent, content);
+ },
+});
diff --git a/tests/rateLimiter.test.ts b/tests/rateLimiter.test.ts
new file mode 100644
index 0000000..2645deb
--- /dev/null
+++ b/tests/rateLimiter.test.ts
@@ -0,0 +1,106 @@
+import { assertEquals } from "@std/assert";
+import { config } from "../config.ts";
+
+Deno.test("RateLimiter - allows first request", () => {
+ // Create a new rate limiter instance for testing
+ class TestRateLimiter {
+ private storage = new Map<string, { count: number; resetTime: number }>();
+
+ isRateLimited(ip: string): boolean {
+ const now = Date.now();
+ const entry = this.storage.get(ip);
+
+ if (!entry || now > entry.resetTime) {
+ this.storage.set(ip, {
+ count: 1,
+ resetTime: now + config.rateLimit.windowMs,
+ });
+ return false;
+ }
+
+ if (entry.count >= config.rateLimit.maxUploads) {
+ return true;
+ }
+
+ entry.count++;
+ return false;
+ }
+ }
+
+ const limiter = new TestRateLimiter();
+ assertEquals(limiter.isRateLimited("127.0.0.1"), false);
+});
+
+Deno.test("RateLimiter - allows requests within limit", () => {
+ class TestRateLimiter {
+ private storage = new Map<string, { count: number; resetTime: number }>();
+
+ isRateLimited(ip: string): boolean {
+ const now = Date.now();
+ const entry = this.storage.get(ip);
+
+ if (!entry || now > entry.resetTime) {
+ this.storage.set(ip, {
+ count: 1,
+ resetTime: now + config.rateLimit.windowMs,
+ });
+ return false;
+ }
+
+ if (entry.count >= config.rateLimit.maxUploads) {
+ return true;
+ }
+
+ entry.count++;
+ return false;
+ }
+ }
+
+ const limiter = new TestRateLimiter();
+ const ip = "192.168.1.1";
+
+ // Make requests up to the limit
+ for (let i = 0; i < config.rateLimit.maxUploads; i++) {
+ assertEquals(limiter.isRateLimited(ip), false, `Request ${i + 1} should be allowed`);
+ }
+
+ // Next request should be blocked
+ assertEquals(limiter.isRateLimited(ip), true, "Request beyond limit should be blocked");
+});
+
+Deno.test("RateLimiter - different IPs have separate limits", () => {
+ class TestRateLimiter {
+ private storage = new Map<string, { count: number; resetTime: number }>();
+
+ isRateLimited(ip: string): boolean {
+ const now = Date.now();
+ const entry = this.storage.get(ip);
+
+ if (!entry || now > entry.resetTime) {
+ this.storage.set(ip, {
+ count: 1,
+ resetTime: now + config.rateLimit.windowMs,
+ });
+ return false;
+ }
+
+ if (entry.count >= config.rateLimit.maxUploads) {
+ return true;
+ }
+
+ entry.count++;
+ return false;
+ }
+ }
+
+ const limiter = new TestRateLimiter();
+
+ // Max out first IP
+ for (let i = 0; i < config.rateLimit.maxUploads; i++) {
+ limiter.isRateLimited("10.0.0.1");
+ }
+ assertEquals(limiter.isRateLimited("10.0.0.1"), true);
+
+ // Second IP should still be allowed
+ assertEquals(limiter.isRateLimited("10.0.0.2"), false);
+});