@openclaw-china/shared 0.1.36 → 0.1.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli/china-setup.ts +1 -1
- package/src/cli/index.ts +2 -2
- package/src/cli/install-hint.ts +95 -95
- package/src/file/file-utils.test.ts +141 -141
- package/src/file/file-utils.ts +284 -284
- package/src/file/index.ts +10 -10
- package/src/index.ts +3 -3
- package/src/logger/index.ts +1 -1
- package/src/logger/logger.ts +51 -51
- package/src/media/index.ts +22 -22
- package/src/media/media-io.ts +328 -328
- package/vitest.config.ts +8 -8
package/package.json
CHANGED
package/src/cli/china-setup.ts
CHANGED
|
@@ -63,7 +63,7 @@ type Option<T extends string> = {
|
|
|
63
63
|
label: string;
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
const PROJECT_REPO = "https://github.com/BytePioneer-AI/
|
|
66
|
+
const PROJECT_REPO = "https://github.com/BytePioneer-AI/openclaw-china";
|
|
67
67
|
const GUIDES_BASE = "https://github.com/BytePioneer-AI/openclaw-china/tree/main/doc/guides";
|
|
68
68
|
const OPENCLAW_HOME = join(homedir(), ".openclaw");
|
|
69
69
|
const DEFAULT_PLUGIN_PATH = join(OPENCLAW_HOME, "extensions");
|
package/src/cli/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from "./china-setup.js";
|
|
2
|
-
export * from "./install-hint.js";
|
|
1
|
+
export * from "./china-setup.js";
|
|
2
|
+
export * from "./install-hint.js";
|
package/src/cli/install-hint.ts
CHANGED
|
@@ -1,95 +1,95 @@
|
|
|
1
|
-
import type { ChannelId } from "./china-setup.js";
|
|
2
|
-
|
|
3
|
-
type LoggerLike = {
|
|
4
|
-
info?: (message: string) => void;
|
|
5
|
-
warn?: (message: string) => void;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export type ChinaInstallHintApiLike = {
|
|
9
|
-
logger?: LoggerLike;
|
|
10
|
-
config?: unknown;
|
|
11
|
-
[key: string]: unknown;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const PROJECT_REPO = "https://github.com/BytePioneer-AI/
|
|
15
|
-
const INSTALL_SETUP_COMMAND = "openclaw china setup";
|
|
16
|
-
const START_GATEWAY_COMMAND = "openclaw gateway --port 18789 --verbose";
|
|
17
|
-
const ANSI_RESET = "\u001b[0m";
|
|
18
|
-
const ANSI_BOLD = "\u001b[1m";
|
|
19
|
-
const ANSI_LINK = "\u001b[1;4;96m";
|
|
20
|
-
const ANSI_BORDER = "\u001b[92m";
|
|
21
|
-
const SUPPORTED_CHANNELS: readonly ChannelId[] = [
|
|
22
|
-
"dingtalk",
|
|
23
|
-
"feishu-china",
|
|
24
|
-
"wecom",
|
|
25
|
-
"wecom-app",
|
|
26
|
-
"qqbot",
|
|
27
|
-
];
|
|
28
|
-
const CHINA_INSTALL_HINT_SHOWN_KEY = Symbol.for("@openclaw-china/china-install-hint-shown");
|
|
29
|
-
|
|
30
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
31
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function hasAnyEnabledChinaChannel(config: unknown): boolean {
|
|
35
|
-
if (!isRecord(config)) {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const channels = config.channels;
|
|
40
|
-
if (!isRecord(channels)) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return SUPPORTED_CHANNELS.some((channelId) => {
|
|
45
|
-
const channelConfig = channels[channelId];
|
|
46
|
-
return isRecord(channelConfig) && channelConfig.enabled === true;
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function hasShownInstallHint(): boolean {
|
|
51
|
-
const root = globalThis as Record<PropertyKey, unknown>;
|
|
52
|
-
return root[CHINA_INSTALL_HINT_SHOWN_KEY] === true;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function markInstallHintShown(): void {
|
|
56
|
-
const root = globalThis as Record<PropertyKey, unknown>;
|
|
57
|
-
root[CHINA_INSTALL_HINT_SHOWN_KEY] = true;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function showChinaInstallHint(api: ChinaInstallHintApiLike): void {
|
|
61
|
-
if (hasShownInstallHint() || hasAnyEnabledChinaChannel(api.config)) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
markInstallHintShown();
|
|
65
|
-
|
|
66
|
-
const lines = [
|
|
67
|
-
`${ANSI_BORDER}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${ANSI_RESET}`,
|
|
68
|
-
" OpenClaw China Channels 已就绪!",
|
|
69
|
-
`${ANSI_BORDER}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${ANSI_RESET}`,
|
|
70
|
-
"",
|
|
71
|
-
"项目仓库:",
|
|
72
|
-
` ${ANSI_LINK}${PROJECT_REPO}${ANSI_RESET}`,
|
|
73
|
-
"",
|
|
74
|
-
"⭐ 如果这个项目对你有帮助,请给我们一个 Star!⭐",
|
|
75
|
-
"",
|
|
76
|
-
"下一步(配置引导):",
|
|
77
|
-
" 1. 运行交互式配置向导",
|
|
78
|
-
` ${ANSI_BOLD}${INSTALL_SETUP_COMMAND}${ANSI_RESET}`,
|
|
79
|
-
" 2. 按提示填写渠道凭据并保存配置",
|
|
80
|
-
" 3. 启动网关并观察日志",
|
|
81
|
-
` ${START_GATEWAY_COMMAND}`,
|
|
82
|
-
];
|
|
83
|
-
|
|
84
|
-
if (api.logger?.info) {
|
|
85
|
-
for (const line of lines) {
|
|
86
|
-
api.logger.info(line);
|
|
87
|
-
}
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (api.logger?.warn) {
|
|
91
|
-
for (const line of lines) {
|
|
92
|
-
api.logger.warn(line);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
1
|
+
import type { ChannelId } from "./china-setup.js";
|
|
2
|
+
|
|
3
|
+
type LoggerLike = {
|
|
4
|
+
info?: (message: string) => void;
|
|
5
|
+
warn?: (message: string) => void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type ChinaInstallHintApiLike = {
|
|
9
|
+
logger?: LoggerLike;
|
|
10
|
+
config?: unknown;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const PROJECT_REPO = "https://github.com/BytePioneer-AI/openclaw-china";
|
|
15
|
+
const INSTALL_SETUP_COMMAND = "openclaw china setup";
|
|
16
|
+
const START_GATEWAY_COMMAND = "openclaw gateway --port 18789 --verbose";
|
|
17
|
+
const ANSI_RESET = "\u001b[0m";
|
|
18
|
+
const ANSI_BOLD = "\u001b[1m";
|
|
19
|
+
const ANSI_LINK = "\u001b[1;4;96m";
|
|
20
|
+
const ANSI_BORDER = "\u001b[92m";
|
|
21
|
+
const SUPPORTED_CHANNELS: readonly ChannelId[] = [
|
|
22
|
+
"dingtalk",
|
|
23
|
+
"feishu-china",
|
|
24
|
+
"wecom",
|
|
25
|
+
"wecom-app",
|
|
26
|
+
"qqbot",
|
|
27
|
+
];
|
|
28
|
+
const CHINA_INSTALL_HINT_SHOWN_KEY = Symbol.for("@openclaw-china/china-install-hint-shown");
|
|
29
|
+
|
|
30
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
31
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function hasAnyEnabledChinaChannel(config: unknown): boolean {
|
|
35
|
+
if (!isRecord(config)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const channels = config.channels;
|
|
40
|
+
if (!isRecord(channels)) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return SUPPORTED_CHANNELS.some((channelId) => {
|
|
45
|
+
const channelConfig = channels[channelId];
|
|
46
|
+
return isRecord(channelConfig) && channelConfig.enabled === true;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function hasShownInstallHint(): boolean {
|
|
51
|
+
const root = globalThis as Record<PropertyKey, unknown>;
|
|
52
|
+
return root[CHINA_INSTALL_HINT_SHOWN_KEY] === true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function markInstallHintShown(): void {
|
|
56
|
+
const root = globalThis as Record<PropertyKey, unknown>;
|
|
57
|
+
root[CHINA_INSTALL_HINT_SHOWN_KEY] = true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function showChinaInstallHint(api: ChinaInstallHintApiLike): void {
|
|
61
|
+
if (hasShownInstallHint() || hasAnyEnabledChinaChannel(api.config)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
markInstallHintShown();
|
|
65
|
+
|
|
66
|
+
const lines = [
|
|
67
|
+
`${ANSI_BORDER}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${ANSI_RESET}`,
|
|
68
|
+
" OpenClaw China Channels 已就绪!",
|
|
69
|
+
`${ANSI_BORDER}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${ANSI_RESET}`,
|
|
70
|
+
"",
|
|
71
|
+
"项目仓库:",
|
|
72
|
+
` ${ANSI_LINK}${PROJECT_REPO}${ANSI_RESET}`,
|
|
73
|
+
"",
|
|
74
|
+
"⭐ 如果这个项目对你有帮助,请给我们一个 Star!⭐",
|
|
75
|
+
"",
|
|
76
|
+
"下一步(配置引导):",
|
|
77
|
+
" 1. 运行交互式配置向导",
|
|
78
|
+
` ${ANSI_BOLD}${INSTALL_SETUP_COMMAND}${ANSI_RESET}`,
|
|
79
|
+
" 2. 按提示填写渠道凭据并保存配置",
|
|
80
|
+
" 3. 启动网关并观察日志",
|
|
81
|
+
` ${START_GATEWAY_COMMAND}`,
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
if (api.logger?.info) {
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
api.logger.info(line);
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (api.logger?.warn) {
|
|
91
|
+
for (const line of lines) {
|
|
92
|
+
api.logger.warn(line);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit Tests for File Utilities
|
|
3
|
-
*
|
|
4
|
-
* Feature: dingtalk-media-receive
|
|
5
|
-
* Validates: Requirements 5.1-5.8, 6.1-6.6
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect } from "vitest";
|
|
9
|
-
import { resolveFileCategory, resolveExtension } from "./file-utils.js";
|
|
10
|
-
|
|
11
|
-
describe("resolveFileCategory", () => {
|
|
12
|
-
// Image categorization (Requirement 5.1)
|
|
13
|
-
it("should categorize image MIME types", () => {
|
|
14
|
-
expect(resolveFileCategory("image/jpeg")).toBe("image");
|
|
15
|
-
expect(resolveFileCategory("image/png")).toBe("image");
|
|
16
|
-
expect(resolveFileCategory("image/gif")).toBe("image");
|
|
17
|
-
expect(resolveFileCategory("image/webp")).toBe("image");
|
|
18
|
-
expect(resolveFileCategory("image/bmp")).toBe("image");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
// Audio categorization (Requirement 5.2)
|
|
22
|
-
it("should categorize audio MIME types", () => {
|
|
23
|
-
expect(resolveFileCategory("audio/mpeg")).toBe("audio");
|
|
24
|
-
expect(resolveFileCategory("audio/wav")).toBe("audio");
|
|
25
|
-
expect(resolveFileCategory("audio/ogg")).toBe("audio");
|
|
26
|
-
expect(resolveFileCategory("audio/amr")).toBe("audio");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// Video categorization (Requirement 5.3)
|
|
30
|
-
it("should categorize video MIME types", () => {
|
|
31
|
-
expect(resolveFileCategory("video/mp4")).toBe("video");
|
|
32
|
-
expect(resolveFileCategory("video/quicktime")).toBe("video");
|
|
33
|
-
expect(resolveFileCategory("video/webm")).toBe("video");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// Document categorization (Requirement 5.4)
|
|
37
|
-
it("should categorize document MIME types", () => {
|
|
38
|
-
expect(resolveFileCategory("application/pdf")).toBe("document");
|
|
39
|
-
expect(resolveFileCategory("application/msword")).toBe("document");
|
|
40
|
-
expect(resolveFileCategory("text/plain")).toBe("document");
|
|
41
|
-
expect(resolveFileCategory("text/markdown")).toBe("document");
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// Archive categorization (Requirement 5.5)
|
|
45
|
-
it("should categorize archive MIME types", () => {
|
|
46
|
-
expect(resolveFileCategory("application/zip")).toBe("archive");
|
|
47
|
-
expect(resolveFileCategory("application/x-rar-compressed")).toBe("archive");
|
|
48
|
-
expect(resolveFileCategory("application/x-7z-compressed")).toBe("archive");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// Code categorization (Requirement 5.6)
|
|
52
|
-
it("should categorize code MIME types", () => {
|
|
53
|
-
expect(resolveFileCategory("application/json")).toBe("code");
|
|
54
|
-
expect(resolveFileCategory("text/html")).toBe("code");
|
|
55
|
-
expect(resolveFileCategory("text/css")).toBe("code");
|
|
56
|
-
expect(resolveFileCategory("text/javascript")).toBe("code");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Extension fallback (Requirement 5.8)
|
|
60
|
-
it("should use extension fallback when MIME type is unknown", () => {
|
|
61
|
-
expect(resolveFileCategory("application/octet-stream", "photo.jpg")).toBe("image");
|
|
62
|
-
expect(resolveFileCategory("application/octet-stream", "song.mp3")).toBe("audio");
|
|
63
|
-
expect(resolveFileCategory("application/octet-stream", "movie.mp4")).toBe("video");
|
|
64
|
-
expect(resolveFileCategory("application/octet-stream", "doc.pdf")).toBe("document");
|
|
65
|
-
expect(resolveFileCategory("application/octet-stream", "archive.zip")).toBe("archive");
|
|
66
|
-
expect(resolveFileCategory("application/octet-stream", "script.py")).toBe("code");
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// Other category (Requirement 5.7)
|
|
70
|
-
it("should return 'other' for unknown types", () => {
|
|
71
|
-
expect(resolveFileCategory("application/octet-stream")).toBe("other");
|
|
72
|
-
expect(resolveFileCategory("application/unknown")).toBe("other");
|
|
73
|
-
expect(resolveFileCategory("application/octet-stream", "file.xyz")).toBe("other");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// MIME type normalization
|
|
77
|
-
it("should handle MIME types with parameters", () => {
|
|
78
|
-
expect(resolveFileCategory("image/jpeg; charset=utf-8")).toBe("image");
|
|
79
|
-
expect(resolveFileCategory("text/plain; charset=utf-8")).toBe("document");
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe("resolveExtension", () => {
|
|
84
|
-
// Image extensions (Requirement 6.1)
|
|
85
|
-
it("should resolve image MIME types to extensions", () => {
|
|
86
|
-
expect(resolveExtension("image/jpeg")).toBe(".jpg");
|
|
87
|
-
expect(resolveExtension("image/png")).toBe(".png");
|
|
88
|
-
expect(resolveExtension("image/gif")).toBe(".gif");
|
|
89
|
-
expect(resolveExtension("image/webp")).toBe(".webp");
|
|
90
|
-
expect(resolveExtension("image/bmp")).toBe(".bmp");
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Audio extensions (Requirement 6.2)
|
|
94
|
-
it("should resolve audio MIME types to extensions", () => {
|
|
95
|
-
expect(resolveExtension("audio/mpeg")).toBe(".mp3");
|
|
96
|
-
expect(resolveExtension("audio/wav")).toBe(".wav");
|
|
97
|
-
expect(resolveExtension("audio/ogg")).toBe(".ogg");
|
|
98
|
-
expect(resolveExtension("audio/amr")).toBe(".amr");
|
|
99
|
-
expect(resolveExtension("audio/x-m4a")).toBe(".m4a");
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// Video extensions (Requirement 6.3)
|
|
103
|
-
it("should resolve video MIME types to extensions", () => {
|
|
104
|
-
expect(resolveExtension("video/mp4")).toBe(".mp4");
|
|
105
|
-
expect(resolveExtension("video/quicktime")).toBe(".mov");
|
|
106
|
-
expect(resolveExtension("video/x-msvideo")).toBe(".avi");
|
|
107
|
-
expect(resolveExtension("video/webm")).toBe(".webm");
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// Document extensions (Requirement 6.4)
|
|
111
|
-
it("should resolve document MIME types to extensions", () => {
|
|
112
|
-
expect(resolveExtension("application/pdf")).toBe(".pdf");
|
|
113
|
-
expect(resolveExtension("application/msword")).toBe(".doc");
|
|
114
|
-
expect(resolveExtension("application/vnd.openxmlformats-officedocument.wordprocessingml.document")).toBe(".docx");
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// Default extension (Requirement 6.5)
|
|
118
|
-
it("should return .bin for unknown MIME types", () => {
|
|
119
|
-
expect(resolveExtension("application/unknown")).toBe(".bin");
|
|
120
|
-
expect(resolveExtension("application/octet-stream")).toBe(".bin");
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// fileName precedence (Requirement 6.6)
|
|
124
|
-
it("should use fileName extension when provided", () => {
|
|
125
|
-
expect(resolveExtension("application/octet-stream", "photo.jpg")).toBe(".jpg");
|
|
126
|
-
expect(resolveExtension("image/png", "custom.jpeg")).toBe(".jpeg");
|
|
127
|
-
expect(resolveExtension("application/unknown", "document.pdf")).toBe(".pdf");
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// MIME type normalization
|
|
131
|
-
it("should handle MIME types with parameters", () => {
|
|
132
|
-
expect(resolveExtension("image/jpeg; charset=utf-8")).toBe(".jpg");
|
|
133
|
-
expect(resolveExtension("audio/mpeg; bitrate=320")).toBe(".mp3");
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// Edge cases
|
|
137
|
-
it("should handle fileName without extension", () => {
|
|
138
|
-
expect(resolveExtension("image/jpeg", "photo")).toBe(".jpg");
|
|
139
|
-
expect(resolveExtension("application/unknown", "noext")).toBe(".bin");
|
|
140
|
-
});
|
|
141
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tests for File Utilities
|
|
3
|
+
*
|
|
4
|
+
* Feature: dingtalk-media-receive
|
|
5
|
+
* Validates: Requirements 5.1-5.8, 6.1-6.6
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect } from "vitest";
|
|
9
|
+
import { resolveFileCategory, resolveExtension } from "./file-utils.js";
|
|
10
|
+
|
|
11
|
+
describe("resolveFileCategory", () => {
|
|
12
|
+
// Image categorization (Requirement 5.1)
|
|
13
|
+
it("should categorize image MIME types", () => {
|
|
14
|
+
expect(resolveFileCategory("image/jpeg")).toBe("image");
|
|
15
|
+
expect(resolveFileCategory("image/png")).toBe("image");
|
|
16
|
+
expect(resolveFileCategory("image/gif")).toBe("image");
|
|
17
|
+
expect(resolveFileCategory("image/webp")).toBe("image");
|
|
18
|
+
expect(resolveFileCategory("image/bmp")).toBe("image");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Audio categorization (Requirement 5.2)
|
|
22
|
+
it("should categorize audio MIME types", () => {
|
|
23
|
+
expect(resolveFileCategory("audio/mpeg")).toBe("audio");
|
|
24
|
+
expect(resolveFileCategory("audio/wav")).toBe("audio");
|
|
25
|
+
expect(resolveFileCategory("audio/ogg")).toBe("audio");
|
|
26
|
+
expect(resolveFileCategory("audio/amr")).toBe("audio");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Video categorization (Requirement 5.3)
|
|
30
|
+
it("should categorize video MIME types", () => {
|
|
31
|
+
expect(resolveFileCategory("video/mp4")).toBe("video");
|
|
32
|
+
expect(resolveFileCategory("video/quicktime")).toBe("video");
|
|
33
|
+
expect(resolveFileCategory("video/webm")).toBe("video");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Document categorization (Requirement 5.4)
|
|
37
|
+
it("should categorize document MIME types", () => {
|
|
38
|
+
expect(resolveFileCategory("application/pdf")).toBe("document");
|
|
39
|
+
expect(resolveFileCategory("application/msword")).toBe("document");
|
|
40
|
+
expect(resolveFileCategory("text/plain")).toBe("document");
|
|
41
|
+
expect(resolveFileCategory("text/markdown")).toBe("document");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Archive categorization (Requirement 5.5)
|
|
45
|
+
it("should categorize archive MIME types", () => {
|
|
46
|
+
expect(resolveFileCategory("application/zip")).toBe("archive");
|
|
47
|
+
expect(resolveFileCategory("application/x-rar-compressed")).toBe("archive");
|
|
48
|
+
expect(resolveFileCategory("application/x-7z-compressed")).toBe("archive");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Code categorization (Requirement 5.6)
|
|
52
|
+
it("should categorize code MIME types", () => {
|
|
53
|
+
expect(resolveFileCategory("application/json")).toBe("code");
|
|
54
|
+
expect(resolveFileCategory("text/html")).toBe("code");
|
|
55
|
+
expect(resolveFileCategory("text/css")).toBe("code");
|
|
56
|
+
expect(resolveFileCategory("text/javascript")).toBe("code");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Extension fallback (Requirement 5.8)
|
|
60
|
+
it("should use extension fallback when MIME type is unknown", () => {
|
|
61
|
+
expect(resolveFileCategory("application/octet-stream", "photo.jpg")).toBe("image");
|
|
62
|
+
expect(resolveFileCategory("application/octet-stream", "song.mp3")).toBe("audio");
|
|
63
|
+
expect(resolveFileCategory("application/octet-stream", "movie.mp4")).toBe("video");
|
|
64
|
+
expect(resolveFileCategory("application/octet-stream", "doc.pdf")).toBe("document");
|
|
65
|
+
expect(resolveFileCategory("application/octet-stream", "archive.zip")).toBe("archive");
|
|
66
|
+
expect(resolveFileCategory("application/octet-stream", "script.py")).toBe("code");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Other category (Requirement 5.7)
|
|
70
|
+
it("should return 'other' for unknown types", () => {
|
|
71
|
+
expect(resolveFileCategory("application/octet-stream")).toBe("other");
|
|
72
|
+
expect(resolveFileCategory("application/unknown")).toBe("other");
|
|
73
|
+
expect(resolveFileCategory("application/octet-stream", "file.xyz")).toBe("other");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// MIME type normalization
|
|
77
|
+
it("should handle MIME types with parameters", () => {
|
|
78
|
+
expect(resolveFileCategory("image/jpeg; charset=utf-8")).toBe("image");
|
|
79
|
+
expect(resolveFileCategory("text/plain; charset=utf-8")).toBe("document");
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("resolveExtension", () => {
|
|
84
|
+
// Image extensions (Requirement 6.1)
|
|
85
|
+
it("should resolve image MIME types to extensions", () => {
|
|
86
|
+
expect(resolveExtension("image/jpeg")).toBe(".jpg");
|
|
87
|
+
expect(resolveExtension("image/png")).toBe(".png");
|
|
88
|
+
expect(resolveExtension("image/gif")).toBe(".gif");
|
|
89
|
+
expect(resolveExtension("image/webp")).toBe(".webp");
|
|
90
|
+
expect(resolveExtension("image/bmp")).toBe(".bmp");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Audio extensions (Requirement 6.2)
|
|
94
|
+
it("should resolve audio MIME types to extensions", () => {
|
|
95
|
+
expect(resolveExtension("audio/mpeg")).toBe(".mp3");
|
|
96
|
+
expect(resolveExtension("audio/wav")).toBe(".wav");
|
|
97
|
+
expect(resolveExtension("audio/ogg")).toBe(".ogg");
|
|
98
|
+
expect(resolveExtension("audio/amr")).toBe(".amr");
|
|
99
|
+
expect(resolveExtension("audio/x-m4a")).toBe(".m4a");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Video extensions (Requirement 6.3)
|
|
103
|
+
it("should resolve video MIME types to extensions", () => {
|
|
104
|
+
expect(resolveExtension("video/mp4")).toBe(".mp4");
|
|
105
|
+
expect(resolveExtension("video/quicktime")).toBe(".mov");
|
|
106
|
+
expect(resolveExtension("video/x-msvideo")).toBe(".avi");
|
|
107
|
+
expect(resolveExtension("video/webm")).toBe(".webm");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Document extensions (Requirement 6.4)
|
|
111
|
+
it("should resolve document MIME types to extensions", () => {
|
|
112
|
+
expect(resolveExtension("application/pdf")).toBe(".pdf");
|
|
113
|
+
expect(resolveExtension("application/msword")).toBe(".doc");
|
|
114
|
+
expect(resolveExtension("application/vnd.openxmlformats-officedocument.wordprocessingml.document")).toBe(".docx");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Default extension (Requirement 6.5)
|
|
118
|
+
it("should return .bin for unknown MIME types", () => {
|
|
119
|
+
expect(resolveExtension("application/unknown")).toBe(".bin");
|
|
120
|
+
expect(resolveExtension("application/octet-stream")).toBe(".bin");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// fileName precedence (Requirement 6.6)
|
|
124
|
+
it("should use fileName extension when provided", () => {
|
|
125
|
+
expect(resolveExtension("application/octet-stream", "photo.jpg")).toBe(".jpg");
|
|
126
|
+
expect(resolveExtension("image/png", "custom.jpeg")).toBe(".jpeg");
|
|
127
|
+
expect(resolveExtension("application/unknown", "document.pdf")).toBe(".pdf");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// MIME type normalization
|
|
131
|
+
it("should handle MIME types with parameters", () => {
|
|
132
|
+
expect(resolveExtension("image/jpeg; charset=utf-8")).toBe(".jpg");
|
|
133
|
+
expect(resolveExtension("audio/mpeg; bitrate=320")).toBe(".mp3");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Edge cases
|
|
137
|
+
it("should handle fileName without extension", () => {
|
|
138
|
+
expect(resolveExtension("image/jpeg", "photo")).toBe(".jpg");
|
|
139
|
+
expect(resolveExtension("application/unknown", "noext")).toBe(".bin");
|
|
140
|
+
});
|
|
141
|
+
});
|