char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somsnapshot: embed shared main and worker startup in the CLI688e96e

main
8.5 KiB230 linesraw
1#[path = "common/http.rs"]
2mod http_fixture;
3
4use http_fixture::Http;
5
6fn cli(directory: &std::path::Path, args: &[&str]) -> std::process::Output {
7    std::process::Command::new(env!("CARGO_BIN_EXE_avril"))
8        .current_dir(directory)
9        .arg(args[0])
10        .arg("--cache-dir=cache")
11        .args(&args[1..])
12        .output()
13        .unwrap()
14}
15
16fn success(output: std::process::Output) -> String {
17    assert!(
18        output.status.success(),
19        "{}",
20        String::from_utf8_lossy(&output.stderr)
21    );
22    String::from_utf8(output.stdout).unwrap()
23}
24
25#[test]
26fn http_graphs_resolve_maps_cycles_queries_and_concurrent_dynamic_imports() {
27    let http = Http::new();
28    let dir = tempfile::tempdir().unwrap();
29    http.source("lib/mod.ts?version=1", "import type {Missing} from './missing.ts'; import './cycle.ts'; export {value} from './dep.mts'; export const count: number = globalThis.count = (globalThis.count ?? 0) + 1; export const url = import.meta.url; export const self = () => import(import.meta.url);");
30    http.source(
31        "lib/cycle.ts",
32        "import {count} from './mod.ts?version=1'; export const read = () => count;",
33    );
34    http.source("lib/dep.mts", "export const value: number = 42;");
35    http.source(
36        "lib/lazy.ts",
37        "import {value} from './dep.mts'; export {value};",
38    );
39    let name = http.name("lib/mod.ts?version=1");
40    std::fs::write(
41        dir.path().join("avril.conf"),
42        format!("imports: {{lib: '{name}'}}"),
43    )
44    .unwrap();
45    std::fs::write(dir.path().join("main.ts"), format!("import * as a from 'lib'; import * as b from '{name}'; const lazy = '{}'; const [x,y] = await Promise.all([import(lazy),import(lazy)]); console.log(a === b, a.value, a.count, (await a.self()) === a, x === y, x.value, a.url);", http.name("lib/lazy.ts"))).unwrap();
46    let expected = format!("true 42 1 true true 42 {name}\n");
47    assert_eq!(success(cli(dir.path(), &["run", "main.ts"])), expected);
48    let requests = http.requests.lock().unwrap().clone();
49    assert_eq!(requests.len(), 4, "{requests:?}");
50    assert_eq!(
51        success(cli(
52            dir.path(),
53            &["run", "--offline", "--frozen", "main.ts"]
54        )),
55        expected
56    );
57    assert_eq!(*http.requests.lock().unwrap(), requests);
58    let info: serde_json::Value =
59        serde_json::from_str(&success(cli(dir.path(), &["info", "--offline", "main.ts"]))).unwrap();
60    assert_eq!(info["modules"][&name]["url"], name);
61    assert_eq!(
62        info["modules"][&name]["imports"]["./dep.mts"],
63        http.name("lib/dep.mts")
64    );
65}
66
67#[test]
68fn redirected_modules_use_the_final_base_without_authorizing_redirect_targets() {
69    let http = Http::new();
70    let target = Http::new();
71    let dir = tempfile::tempdir().unwrap();
72    http.redirect("start", &target.name("nested/mod.ts"));
73    target.source("nested/mod.ts", "export const value: number = 7; export const url = import.meta.url; export const self = () => import(import.meta.url);");
74    let name = http.name("start");
75    std::fs::write(
76        dir.path().join("main.ts"),
77        format!(
78            "import * as a from '{name}'; console.log(a.value, a.url, (await a.self()) === a);"
79        ),
80    )
81    .unwrap();
82    let allow = format!("--allow-js-exec={name}");
83    let deny = format!("--deny-js-exec={}", target.url);
84    let args = ["run", "-0", &allow, &deny, "main.ts"];
85    let expected = format!("7 {name} true\n");
86    assert_eq!(success(cli(dir.path(), &args)), expected);
87    let mut offline = args.to_vec();
88    offline.insert(1, "--offline");
89    assert_eq!(success(cli(dir.path(), &offline)), expected);
90    target.source("nested/mod.ts", "import {value} from './dep.ts'; export {value}; export const url = import.meta.url; export const self = () => import(import.meta.url);");
91    target.source("nested/dep.ts", "export const value: number = 9;");
92    let output = cli(dir.path(), &["update", "-0", &allow, &deny, "main.ts"]);
93    assert!(!output.status.success());
94    assert!(String::from_utf8_lossy(&output.stderr).contains("PermissionDenied"));
95    assert!(
96        !target
97            .requests
98            .lock()
99            .unwrap()
100            .iter()
101            .any(|path| path.ends_with("dep.ts"))
102    );
103    let dep = target.name("nested/dep.ts");
104    let allow_dep = format!("--allow-js-exec={dep}");
105    success(cli(
106        dir.path(),
107        &["update", "-0", &allow, &allow_dep, "main.ts"],
108    ));
109    assert_eq!(
110        success(cli(
111            dir.path(),
112            &[
113                "run",
114                "--offline",
115                "--frozen",
116                "-0",
117                &allow,
118                &allow_dep,
119                "main.ts"
120            ]
121        )),
122        expected.replace("7 ", "9 ")
123    );
124}
125
126#[test]
127fn update_refreshes_cli_graphs_and_http_urls_can_be_entries() {
128    let http = Http::new();
129    let dir = tempfile::tempdir().unwrap();
130    let name = http.name("entry.ts?x=1");
131    http.source(
132        "entry.ts?x=1",
133        "console.log(1, import.meta.main, import.meta.url); export {};",
134    );
135    let expected = format!("1 true {name}\n");
136    assert_eq!(success(cli(dir.path(), &["run", "-0", &name])), expected);
137    http.source(
138        "entry.ts?x=1",
139        "console.log(2, import.meta.main, import.meta.url); export {};",
140    );
141    assert_eq!(success(cli(dir.path(), &["run", "-0", &name])), expected);
142    success(cli(dir.path(), &["update", &name]));
143    assert_eq!(
144        success(cli(
145            dir.path(),
146            &["run", "--offline", "--frozen", "-0", &name]
147        )),
148        expected.replace("1 true", "2 true")
149    );
150    let deny = format!("--deny-js-exec={}", http.url);
151    let output = cli(dir.path(), &["run", "--offline", "-0", &deny, &name]);
152    assert!(!output.status.success());
153    assert!(String::from_utf8_lossy(&output.stderr).contains("PermissionDenied"));
154}
155
156#[test]
157fn http_modules_do_not_inherit_local_aliases_or_resolve_file_urls() {
158    let http = Http::new();
159    let dir = tempfile::tempdir().unwrap();
160    std::fs::write(dir.path().join("avril.conf"), "imports: {dep: './dep.ts'}").unwrap();
161    std::fs::write(
162        dir.path().join("dep.ts"),
163        "throw new Error('local file executed');",
164    )
165    .unwrap();
166    for (specifier, expected) in [
167        ("dep", "Unsupported module specifier: dep"),
168        (
169            "file:///tmp/dep.ts",
170            "Unsupported module specifier: file:///tmp/dep.ts",
171        ),
172        (
173            "node:fs",
174            "node: builtin modules are not supported: node:fs",
175        ),
176    ] {
177        http.source("mod.js", &format!("import '{specifier}';"));
178        std::fs::write(
179            dir.path().join("main.ts"),
180            format!("import '{}';", http.name("mod.js")),
181        )
182        .unwrap();
183        let output = cli(dir.path(), &["update", "main.ts"]);
184        assert!(!output.status.success());
185        assert!(
186            String::from_utf8_lossy(&output.stderr).contains(expected),
187            "{specifier}: {}",
188            String::from_utf8_lossy(&output.stderr)
189        );
190    }
191}
192
193#[test]
194fn http_sources_are_vendored_and_usable_offline() {
195    let http = Http::new();
196    let dir = tempfile::tempdir().unwrap();
197    let name = http.name("start");
198    http.redirect("start", "/nested/mod.ts");
199    http.source("nested/mod.ts", "export {value} from './dep.ts';");
200    http.source("nested/dep.ts", "export const value: number = 42;");
201    std::fs::write(
202        dir.path().join("main.ts"),
203        format!("import {{value}} from '{name}'; console.log(value);"),
204    )
205    .unwrap();
206    success(cli(dir.path(), &["vendor", "main.ts"]));
207    std::fs::remove_dir_all(dir.path().join("cache")).unwrap();
208    assert_eq!(
209        success(cli(
210            dir.path(),
211            &["run", "--vendor=vendor", "--offline", "--frozen", "main.ts"]
212        )),
213        "42\n"
214    );
215}
216
217#[test]
218fn complete_http_modules_obey_live_revocation_of_the_requested_url() {
219    let http = Http::new();
220    let target = Http::new();
221    let dir = tempfile::tempdir().unwrap();
222    let name = http.name("start");
223    http.redirect("start", &target.name("mod.ts"));
224    target.source("mod.ts", "export const value = 42;");
225    std::fs::write(dir.path().join("main.ts"), format!("import {{pledge}} from 'avril:std'; const a = await import('{name}'); pledge({{jsExec: {{deny: ['{}']}}}}); console.log((await import('{name}')) === a); pledge({{jsExec: {{deny: ['{name}']}}}}); try {{await import('{name}'); console.log('allowed');}} catch(e) {{console.log(e.message.includes('PermissionDenied'));}}", target.url)).unwrap();
226    assert_eq!(
227        success(cli(dir.path(), &["run", "main.ts"])),
228        "true\ntrue\n"
229    );
230}