quickly upload files to a remote server via rsync
1import { describe, test, expect } from "bun:test";
2import { formatTimestamp, validateFilename } from "./utils";
3
4describe("formatTimestamp", () => {
5 test("formats date correctly", () => {
6 const date = new Date(2024, 0, 15, 14, 30, 45); // Jan 15, 2024 14:30:45
7 const result = formatTimestamp(date);
8 expect(result).toBe("2024-01-15 at 14.30.45");
9 });
10
11 test("pads single digit values", () => {
12 const date = new Date(2024, 2, 5, 9, 5, 5); // Mar 5, 2024 09:05:05
13 const result = formatTimestamp(date);
14 expect(result).toBe("2024-03-05 at 09.05.05");
15 });
16
17 test("handles midnight", () => {
18 const date = new Date(2024, 0, 1, 0, 0, 0);
19 const result = formatTimestamp(date);
20 expect(result).toBe("2024-01-01 at 00.00.00");
21 });
22
23 test("handles end of day", () => {
24 const date = new Date(2024, 0, 1, 23, 59, 59);
25 const result = formatTimestamp(date);
26 expect(result).toBe("2024-01-01 at 23.59.59");
27 });
28});
29
30describe("validateFilename", () => {
31 test("accepts valid filename", () => {
32 const result = validateFilename("my-file.txt");
33 expect(result.ok).toBe(true);
34 if (result.ok) {
35 expect(result.value).toBe("my-file.txt");
36 }
37 });
38
39 test("accepts filename with spaces", () => {
40 const result = validateFilename("my file.txt");
41 expect(result.ok).toBe(true);
42 });
43
44 test("accepts unicode characters", () => {
45 const result = validateFilename("文件.txt");
46 expect(result.ok).toBe(true);
47 });
48
49 test("rejects empty string", () => {
50 const result = validateFilename("");
51 expect(result.ok).toBe(false);
52 if (!result.ok) {
53 expect(result.error).toBe("empty");
54 }
55 });
56
57 test("rejects whitespace-only string", () => {
58 const result = validateFilename(" ");
59 expect(result.ok).toBe(false);
60 if (!result.ok) {
61 expect(result.error).toBe("empty");
62 }
63 });
64
65 test("rejects path traversal with ..", () => {
66 const result = validateFilename("../etc/passwd");
67 expect(result.ok).toBe(false);
68 if (!result.ok) {
69 expect(result.error).toBe("path_traversal");
70 }
71 });
72
73 test("rejects path traversal with /", () => {
74 const result = validateFilename("/etc/passwd");
75 expect(result.ok).toBe(false);
76 if (!result.ok) {
77 expect(result.error).toBe("path_traversal");
78 }
79 });
80
81 test("rejects path traversal with backslash", () => {
82 const result = validateFilename("file\\..\\passwd");
83 expect(result.ok).toBe(false);
84 if (!result.ok) {
85 expect(result.error).toBe("path_traversal");
86 }
87 });
88
89 test("rejects filename with null byte", () => {
90 const result = validateFilename("file\x00.txt");
91 expect(result.ok).toBe(false);
92 if (!result.ok) {
93 expect(result.error).toBe("invalid_chars");
94 }
95 });
96
97 test("rejects filename with control characters", () => {
98 const result = validateFilename("file\x01.txt");
99 expect(result.ok).toBe(false);
100 if (!result.ok) {
101 expect(result.error).toBe("invalid_chars");
102 }
103 });
104
105 test("rejects filename exceeding max length", () => {
106 const longName = "a".repeat(256);
107 const result = validateFilename(longName);
108 expect(result.ok).toBe(false);
109 if (!result.ok) {
110 expect(result.error).toBe("too_long");
111 }
112 });
113
114 test("accepts filename at max length", () => {
115 const maxName = "a".repeat(255);
116 const result = validateFilename(maxName);
117 expect(result.ok).toBe(true);
118 });
119
120 test("trims whitespace from valid filename", () => {
121 const result = validateFilename(" file.txt ");
122 expect(result.ok).toBe(true);
123 if (result.ok) {
124 expect(result.value).toBe("file.txt");
125 }
126 });
127});