File size: 8,968 Bytes
7a4c980
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/**
 * Test for secret key validation in the actual Invidious companion configuration
 * This test verifies that SERVER_SECRET_KEY validation properly rejects special characters
 * when the actual config is parsed
 */
import { assert, assertEquals } from "./deps.ts";
import { parseConfig } from "../lib/helpers/config.ts";

Deno.test("Secret key validation in Invidious companion config", async (t) => {
    // Clean up any existing environment variables that might interfere
    Deno.env.delete("SERVER_SECRET_KEY");

    await t.step("accepts valid alphanumeric keys", async () => {
        const validKeys = [
            "aaaaaaaaaaaaaaaa", // all lowercase
            "AAAAAAAAAAAAAAAA", // all uppercase
            "1234567890123456", // all numbers
            "Aa1Bb2Cc3Dd4Ee5F", // mixed case
            "ABC123DEF456789A", // mixed letters and numbers
        ];

        for (const key of validKeys) {
            // Set the environment variable for each test
            Deno.env.set("SERVER_SECRET_KEY", key);

            try {
                const config = await parseConfig();
                assertEquals(
                    config.server.secret_key,
                    key,
                    `Key "${key}" should be accepted and stored correctly`,
                );
            } catch (error) {
                assert(
                    false,
                    `Key "${key}" should be valid but config parsing failed: ${
                        error instanceof Error ? error.message : String(error)
                    }`,
                );
            }
        }
    });

    await t.step("rejects keys with special characters", async () => {
        const invalidKeys = [
            "my#key!123456789", // Contains # and !
            "test@key12345678", // Contains @ (fixed length)
            "key-with-dashes1", // Contains -
            "key_with_under_s", // Contains _
            "key with spaces1", // Contains spaces (fixed length to 16)
            "key$with$dollar$", // Contains $
            "key+with+plus+12", // Contains +
            "key=with=equals=", // Contains =
            "key(with)parens1", // Contains ()
            "key[with]bracket", // Contains []
        ];

        for (const key of invalidKeys) {
            // Set the environment variable for each test
            Deno.env.set("SERVER_SECRET_KEY", key);

            try {
                await parseConfig();
                assert(
                    false,
                    `Key "${key}" should be invalid but config parsing succeeded`,
                );
            } catch (error) {
                // Verify it's a config parsing error with the right message
                assert(
                    error instanceof Error &&
                        error.message.includes("Failed to parse configuration"),
                    `Should get config parsing error, got: ${
                        error instanceof Error ? error.message : String(error)
                    }`,
                );

                // Check that the error contains expected validation message content
                const errorStr = error instanceof Error
                    ? error.toString()
                    : String(error);
                assert(
                    errorStr.includes(
                        "SERVER_SECRET_KEY contains invalid characters",
                    ) ||
                        errorStr.includes("alphanumeric characters"),
                    `Error should mention invalid characters or alphanumeric, got: ${errorStr}`,
                );
            }
        }
    });

    await t.step("rejects keys with wrong length", async () => {
        const wrongLengthKeys = [
            "short", // Too short
            "thiskeyistoolongtobevalid", // Too long
            "", // Empty
            "a", // Single character
            "exactly15chars", // 15 chars
            "exactly17charss", // 17 chars
        ];

        for (const key of wrongLengthKeys) {
            // Set the environment variable for each test
            Deno.env.set("SERVER_SECRET_KEY", key);

            try {
                await parseConfig();
                assert(
                    false,
                    `Key "${key}" (length ${key.length}) should be invalid but config parsing succeeded`,
                );
            } catch (error) {
                // Verify it's a config parsing error
                assert(
                    error instanceof Error &&
                        error.message.includes("Failed to parse configuration"),
                    `Should get config parsing error, got: ${
                        error instanceof Error ? error.message : String(error)
                    }`,
                );

                // Check that the error mentions length requirement
                const errorStr = error instanceof Error
                    ? error.toString()
                    : String(error);
                assert(
                    errorStr.includes("exactly 16 character") ||
                        errorStr.includes(
                            "String must contain exactly 16 character",
                        ),
                    `Error should mention 16 characters, got: ${errorStr}`,
                );
            }
        }
    });

    await t.step("validates error message content", async () => {
        // Test that special character validation provides the right error
        Deno.env.set("SERVER_SECRET_KEY", "my#key!123456789");

        try {
            await parseConfig();
            assert(false, "Should have failed with special character key");
        } catch (error) {
            const errorStr = error instanceof Error
                ? error.toString()
                : String(error);

            // Check that the error message contains validation details
            assert(
                errorStr.includes(
                    "SERVER_SECRET_KEY contains invalid characters",
                ) ||
                    errorStr.includes("alphanumeric characters"),
                "Should mention SERVER_SECRET_KEY and character validation",
            );
        }

        // Test that length validation still works and provides clear message
        Deno.env.set("SERVER_SECRET_KEY", "short");

        try {
            await parseConfig();
            assert(false, "Should have failed with short key");
        } catch (error) {
            const errorStr = error instanceof Error
                ? error.toString()
                : String(error);
            assert(
                errorStr.includes("exactly 16 character") ||
                    errorStr.includes(
                        "String must contain exactly 16 character",
                    ),
                `Should mention 16 characters: ${errorStr}`,
            );
        }
    });

    await t.step(
        "validates precedence - length vs character validation",
        async () => {
            // When both length and character validation fail, length should be checked first
            // This is the default Zod behavior
            Deno.env.set("SERVER_SECRET_KEY", "bad#");

            try {
                await parseConfig();
                assert(
                    false,
                    "Should have failed with short key containing special chars",
                );
            } catch (error) {
                const errorStr = error instanceof Error
                    ? error.toString()
                    : String(error);
                // Should get length error since it's checked first
                assert(
                    errorStr.includes("exactly 16 character") ||
                        errorStr.includes(
                            "String must contain exactly 16 character",
                        ),
                    `Should get length error first: ${errorStr}`,
                );
            }
        },
    );

    // Clean up environment variable after tests
    await t.step("validates missing SERVER_SECRET_KEY fails", async () => {
        // Test with no SERVER_SECRET_KEY set (uses default empty string)
        Deno.env.delete("SERVER_SECRET_KEY");

        try {
            await parseConfig();
            assert(
                false,
                "Should have failed with missing/empty SERVER_SECRET_KEY",
            );
        } catch (error) {
            const errorStr = error instanceof Error
                ? error.toString()
                : String(error);
            assert(
                errorStr.includes("exactly 16 character") ||
                    errorStr.includes(
                        "String must contain exactly 16 character",
                    ),
                `Should get length error for empty key: ${errorStr}`,
            );
        }
    });

    await t.step("cleanup", () => {
        Deno.env.delete("SERVER_SECRET_KEY");
    });
});