Skip to main content

max / makenotwork

8.0 KB · 227 lines History Blame Raw
1 //! Tests for [`super`].
2
3 use super::*;
4
5 #[test]
6 fn the_update_hook_guards_the_namespace_validation_reserves() {
7 // Bash cannot read a Rust constant, so the literal in the hook is a
8 // copy. Renaming the reserved prefix without editing the hook would
9 // leave the new one pushable and the old one locked, which is the
10 // failure this pins: two doors, one policy.
11 let reserved = crate::validation::RESERVED_NOTE_NAMESPACE;
12 assert!(
13 UPDATE_HOOK.contains(&format!("refs/notes/{reserved}|refs/notes/{reserved}/*")),
14 "the update hook does not guard refs/notes/{reserved}/*:\n{UPDATE_HOOK}"
15 );
16 // The bare prefix and the subtree are separate patterns in a glob, and
17 // matching only the subtree would leave `refs/notes/mnw` itself open.
18 assert!(UPDATE_HOOK.contains("exit 1"), "{UPDATE_HOOK}");
19 }
20
21 #[tokio::test]
22 async fn read_capped_truncates_to_cap() {
23 // 10k bytes through a 4k cap retains exactly 4k (the rest is drained and
24 // discarded so the child never blocks on a full pipe).
25 let data = vec![b'x'; 10_000];
26 let out = read_capped(&data[..], 4096).await;
27 assert_eq!(out.len(), 4096);
28 }
29
30 #[tokio::test]
31 async fn read_capped_returns_all_when_under_cap() {
32 let out = read_capped(&b"hello world"[..], 4096).await;
33 assert_eq!(out, "hello world");
34 }
35
36 #[test]
37 fn build_failure_message_partial() {
38 assert_eq!(
39 build_failure_message(1, 2, Some("boom")),
40 "partial build failure (1/3 targets succeeded)"
41 );
42 assert_eq!(
43 build_failure_message(2, 1, Some("boom")),
44 "partial build failure (2/3 targets succeeded)"
45 );
46 }
47
48 #[test]
49 fn build_failure_message_total_failure_uses_first_error() {
50 assert_eq!(build_failure_message(0, 3, Some("ssh down")), "ssh down");
51 assert_eq!(
52 build_failure_message(0, 0, None),
53 "no targets produced artifacts"
54 );
55 }
56
57 #[test]
58 fn rust_target_mapping() {
59 assert_eq!(
60 rust_target("linux", "x86_64"),
61 Some("x86_64-unknown-linux-gnu")
62 );
63 assert_eq!(
64 rust_target("linux", "aarch64"),
65 Some("aarch64-unknown-linux-gnu")
66 );
67 assert_eq!(rust_target("darwin", "x86_64"), Some("x86_64-apple-darwin"));
68 assert_eq!(
69 rust_target("darwin", "aarch64"),
70 Some("aarch64-apple-darwin")
71 );
72 assert_eq!(rust_target("windows", "x86_64"), None);
73 }
74
75 #[test]
76 fn hook_template_contains_hmac_not_raw_token() {
77 let hook = post_receive_hook("secret-token-123", "alice", "myrepo");
78 let expected_hmac = repo_hmac("secret-token-123", "alice", "myrepo");
79 assert!(
80 hook.contains(&expected_hmac),
81 "hook should contain per-repo HMAC"
82 );
83 assert!(
84 !hook.contains("secret-token-123"),
85 "hook must not contain raw token"
86 );
87 assert!(!hook.contains("__HMAC__"), "placeholder should be replaced");
88 assert!(hook.contains("/api/internal/builds/trigger"));
89 }
90
91 /// The two notes arms answer different refs and must not be confused for
92 /// each other: an inbox push is merged and answered synchronously, a notes
93 /// push is only indexed. A `case` pattern that caught both would either
94 /// merge a ref that is already the namespace or leave a push unindexed.
95 #[test]
96 fn the_hook_indexes_a_notes_push_and_merges_an_inbox_push() {
97 let hook = post_receive_hook("secret-token-123", "alice", "myrepo");
98 assert!(hook.contains("/api/internal/notes/reindex"));
99 assert!(hook.contains("/api/internal/notes/merge-inbox"));
100 assert!(hook.contains("refs/notes/*)"));
101 assert!(hook.contains("refs/mnw/notes-inbox/*)"));
102 // The inbox lives under refs/mnw/, so nothing an inbox push does can
103 // fall into the indexing arm. `notes_inbox` pins that prefix itself.
104 assert!(!"refs/mnw/notes-inbox/commits".starts_with("refs/notes/"));
105 }
106
107 /// Fixed vector, duplicated in mnw-cli's `repo_hmac` test. mnw-cli installs
108 /// hooks for repos it auto-creates over SSH, and this endpoint verifies
109 /// them; if either side's derivation moves, both tests have to move
110 /// together or those pushes stop triggering builds.
111 #[test]
112 fn repo_hmac_matches_mnw_cli_vector() {
113 assert_eq!(
114 repo_hmac("test-token", "max", "repo"),
115 "198b4a4f542c0c27c4ca333030dfe09fe670aa44bae34d7a586481bb13fd9eb0"
116 );
117 }
118
119 #[test]
120 fn repo_hmac_differs_per_repo() {
121 let h1 = repo_hmac("token", "alice", "repo-a");
122 let h2 = repo_hmac("token", "alice", "repo-b");
123 assert_ne!(h1, h2, "different repos should produce different HMACs");
124 }
125
126 #[test]
127 fn shell_escape_basic() {
128 assert_eq!(shell_escape("hello"), "'hello'");
129 assert_eq!(shell_escape("it's"), "'it'\\''s'");
130 }
131
132 #[test]
133 fn validate_build_command_accepts_safe_commands() {
134 assert!(
135 validate_build_command("cargo build --release --target x86_64-unknown-linux-gnu").is_ok()
136 );
137 assert!(validate_build_command("make -j4").is_ok());
138 assert!(validate_build_command("RUSTFLAGS=--cfg tokio_unstable cargo build").is_ok());
139 }
140
141 #[test]
142 fn validate_build_command_rejects_injection() {
143 assert!(validate_build_command("cargo build; curl evil.com").is_err());
144 assert!(validate_build_command("cargo build && rm -rf /").is_err());
145 assert!(validate_build_command("cargo build | tee log").is_err());
146 assert!(validate_build_command("$(whoami)").is_err());
147 assert!(validate_build_command("`whoami`").is_err());
148 assert!(validate_build_command("cargo build > /dev/null").is_err());
149 assert!(validate_build_command("").is_err());
150 assert!(
151 validate_build_command(" ").is_err(),
152 "whitespace-only has no program"
153 );
154 assert!(
155 validate_build_command("FOO=bar").is_err(),
156 "assignment with no program"
157 );
158 }
159
160 #[test]
161 fn remote_command_parse_separates_env_program_args() {
162 let c = RemoteCommand::parse("cargo build --release").unwrap();
163 assert!(c.assignments.is_empty());
164 assert_eq!(c.program, "cargo");
165 assert_eq!(c.args, vec!["build", "--release"]);
166
167 let c = RemoteCommand::parse("RUSTFLAGS=--cfg CARGO_INCREMENTAL=0 cargo build").unwrap();
168 assert_eq!(
169 c.assignments,
170 vec!["RUSTFLAGS=--cfg", "CARGO_INCREMENTAL=0"]
171 );
172 assert_eq!(c.program, "cargo");
173 assert_eq!(c.args, vec!["build"]);
174 }
175
176 #[test]
177 fn remote_command_render_escapes_every_token() {
178 // Plain command: each token individually single-quoted.
179 let c = RemoteCommand::parse("cargo build --release").unwrap();
180 assert_eq!(c.render(), "'cargo' 'build' '--release'");
181
182 // Env prefix: applied via `env`, each element escaped.
183 let c = RemoteCommand::parse("RUSTFLAGS=--cfg cargo build").unwrap();
184 assert_eq!(c.render(), "env 'RUSTFLAGS=--cfg' 'cargo' 'build'");
185 }
186
187 #[test]
188 fn is_env_assignment_recognizes_valid_identifiers_only() {
189 assert!(is_env_assignment("FOO=bar"));
190 assert!(is_env_assignment("_X1=y"));
191 assert!(is_env_assignment("A=")); // empty value is a valid assignment
192 assert!(
193 !is_env_assignment("1FOO=bar"),
194 "identifier can't start with a digit"
195 );
196 assert!(!is_env_assignment("cargo"), "no '='");
197 assert!(!is_env_assignment("--target=x"), "not a shell identifier");
198 }
199
200 #[test]
201 fn render_defuses_would_be_injection_even_if_charset_bypassed() {
202 // Construct a RemoteCommand directly with a hostile arg (bypassing the
203 // token charset check) to prove render() is the real guard: the shell
204 // sees a single quoted word, not a command separator.
205 let c = RemoteCommand {
206 assignments: vec![],
207 program: "cargo".to_string(),
208 args: vec!["build; rm -rf /".to_string()],
209 };
210 assert_eq!(c.render(), "'cargo' 'build; rm -rf /'");
211 }
212
213 #[test]
214 fn validate_artifact_path_accepts_safe_paths() {
215 assert!(validate_artifact_path("target/x86_64-unknown-linux-gnu/release/myapp").is_ok());
216 assert!(validate_artifact_path("dist/app-v0.1.0.tar.gz").is_ok());
217 }
218
219 #[test]
220 fn validate_artifact_path_rejects_unsafe() {
221 assert!(validate_artifact_path("/etc/passwd").is_err());
222 assert!(validate_artifact_path("../../../etc/passwd").is_err());
223 assert!(validate_artifact_path("path with spaces").is_err());
224 assert!(validate_artifact_path("$(whoami)").is_err());
225 assert!(validate_artifact_path("").is_err());
226 }
227