14530604 Andy McCall 1 month ago ↶
1
2
3
4
5
6
use axum:: body:: Body;
7
use axum:: http:: { header, Request, StatusCode} ;
8
use http_body_util:: BodyExt;
9
use oxy_core:: repo:: { IdentityScope, Repo} ;
10
use oxygenhub:: config:: Config;
11
use oxygenhub:: db;
12
use oxygenhub:: repos:: RepoName;
13
use oxygenhub:: Shared;
14
use std:: path:: Path;
15
use tower:: ServiceExt;
16
17
fn hermetic ( ) {
18
static ONCE : std:: sync:: Once = std:: sync:: Once:: new( ) ;
19
static DIR : std:: sync:: OnceLock< tempfile:: TempDir> = std:: sync:: OnceLock:: new( ) ;
20
ONCE .call_once ( | | {
21
let d = tempfile:: tempdir( ) .unwrap ( ) ;
22
std:: env:: set_var( " OXY_HOME" , d.path ( ) ) ;
23
DIR .set ( d) .ok ( ) ;
24
} ) ;
25
}
26
27
fn write ( root : & Path, rel : & str , content : & [u8 ]) {
28
let p = root.join ( rel) ;
29
std:: fs:: create_dir_all( p.parent ( ) .unwrap ( ) ) .unwrap ( ) ;
30
std:: fs:: write( p, content) .unwrap ( ) ;
31
}
32
33
struct Res {
34
status : StatusCode,
35
body : String,
36
cookie : Option< String> ,
37
location : Option< String> ,
38
}
39
40
async fn send ( app : & Shared, method : & str , path : & str , cookie : Option< & str > , form : Option< & str > ) -> Res {
41
let mut req = Request:: builder( ) .method ( method) .uri ( path) ;
42
if let Some ( c) = cookie {
43
req = req.header ( header:: COOKIE , format! ( " oxyhub={c} " ) ) ;
44
}
45
let body = match form {
46
Some ( f) => {
47
req = req.header ( header:: CONTENT_TYPE , " application/x-www-form-urlencoded" ) ;
48
Body:: from( f.to_string ( ) )
49
}
50
None => Body:: empty( ) ,
51
} ;
52
let res = oxygenhub:: router( app.clone ( ) ) .oneshot ( req.body ( body) .unwrap ( ) ) .await.unwrap ( ) ;
53
let status = res.status ( ) ;
54
let cookie = res
55
.headers ( )
56
.get ( header:: SET_COOKIE )
57
.and_then ( | v | v.to_str ( ) .ok ( ) )
58
.and_then ( | v | v.split ( ' ;' ) .next ( ) )
59
.and_then ( | kv | kv.split_once ( ' =' ) )
60
.map ( | ( _, v ) | v.to_string ( ) ) ;
61
let location = res.headers ( ) .get ( header:: LOCATION ) .and_then ( | v | v.to_str ( ) .ok ( ) ) .map ( String :: from) ;
62
let bytes = res.into_body ( ) .collect ( ) .await.unwrap ( ) .to_bytes ( ) ;
63
Res { status, body: String :: from_utf8_lossy( & bytes) .into_owned ( ) , cookie, location }
64
}
65
5e6d6484 Andy McCall 1 month ago ↶
66
67
async fn next_event ( body : & mut Body, kind : & str ) -> String {
68
let mut buf = String :: new( ) ;
69
loop {
70
let frame = tokio:: time:: timeout( std:: time:: Duration:: from_secs( 5 ) , body.frame ( ) )
71
.await
72
.unwrap_or_else ( | _| panic! ( " no {kind} event; got {buf}" ) )
73
.expect ( " the stream ended" )
74
.unwrap ( ) ;
75
if let Ok ( data) = frame.into_data ( ) {
76
buf.push_str ( & String :: from_utf8_lossy( & data) ) ;
77
}
78
while let Some ( end) = buf.find ( " \n \n " ) {
79
let event: String = buf.drain ( .. end + 2 ) .collect ( ) ;
80
if event.contains ( & format! ( " event: {kind} \n " ) ) {
81
return event;
82
}
83
}
84
}
85
}
86
c7067c7b Andy McCall 17 days ago
87
88
89
async fn settled ( app : & Shared, r : Res, cookie : Option< & str > , page : & str ) -> Res {
90
assert_eq! ( r.status, StatusCode:: SEE_OTHER , " {}" , r.body) ;
91
let to = r.location.expect ( " a redirect" ) ;
92
assert! ( to.starts_with ( & format! ( " {page} ?" ) ) , " {to} is not {page}" ) ;
93
send ( app, " GET" , & to, cookie, None ) .await
94
}
95
14530604 Andy McCall 1 month ago ↶
96
fn csrf_of ( page : & str ) -> String {
97
let i = page.find ( " name=\" _csrf\" value=\" " ) .expect ( " csrf field" ) + " name=\" _csrf\" value=\" " .len ( ) ;
98
page[ i.. ] .split ( ' "' ) .next ( ) .unwrap ( ) .to_string ( )
99
}
100
101
fn enc ( s : & str ) -> String {
102
let mut out = String :: new( ) ;
103
for b in s.bytes ( ) {
104
match b {
105
b ' A' .. = b ' Z' | b ' a' .. = b ' z' | b ' 0' .. = b ' 9' | b ' -' | b ' _' | b ' .' | b ' ~' => out.push ( b as char ) ,
106
_ => out.push_str ( & format! ( " %{b:02X} " ) ) ,
107
}
108
}
109
out
110
}
111
112
# [ tokio ::test ( flavor = " multi_thread" ) ]
113
async fn web_edits_under_a_policy ( ) {
114
hermetic ( ) ;
115
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
116
let mut config = Config:: load( dir.path ( ) ) .unwrap ( ) ;
c8684d44 Andy McCall 20 days ago
117
118
config.registration.setup_code = " test-setup" .into ( ) ;
14530604 Andy McCall 1 month ago ↶
119
config.auth.push_tokens = vec! [ " site" .into ( ) ] ;
120
config.registration.mode = " open" .into ( ) ;
121
let listener = tokio:: net:: TcpListener:: bind( " 127.0.0.1:0" ) .await.unwrap ( ) ;
122
let port = listener.local_addr ( ) .unwrap ( ) .port ( ) ;
123
config.server.base_url = format! ( " http://127.0.0.1:{port} " ) ;
124
let app = oxygenhub:: build( config) .await.unwrap ( ) ;
125
let router = oxygenhub:: router( app.clone ( ) ) ;
126
tokio:: spawn( async move { axum:: serve( listener, router) .await.unwrap ( ) } ) ;
127
let base = format! ( " http://127.0.0.1:{port} " ) ;
128
let app = & app;
129
c8684d44 Andy McCall 20 days ago
130
let r =
131
send ( app, " POST" , " /register" , None , Some ( " username=andy&email=a%40example.com&password=correct-horse-battery&setup=test-setup" ) )
132
.await;
14530604 Andy McCall 1 month ago ↶
133
let andy = r.cookie.unwrap ( ) ;
134
let andy_id = db:: accounts:: account_by_name( & app.db, " andy" ) .await.unwrap ( ) .unwrap ( ) .id;
135
let repo = RepoName:: parse( " andy/demo" ) .unwrap ( ) ;
136
app.repos.create ( & repo, None ) .unwrap ( ) ;
137
db:: repos:: create( & app.db, " andy" , " demo" , " " , hub_api:: Visibility:: Public) .await.unwrap ( ) ;
138
139
140
let work = tempfile:: tempdir( ) .unwrap ( ) ;
141
let mut client = Repo:: init( work.path ( ) ) .unwrap ( ) ;
142
client.create_identity ( " Andy McCall" , " [email protected] " , IdentityScope:: Repo) .unwrap ( ) ;
143
client.config.set ( " core.sign" , " change" ) .unwrap ( ) ;
144
client.config.set ( " remote.origin.url" , & format! ( " {base} /andy/demo" ) ) .unwrap ( ) ;
145
client.config.set ( " remote.origin.token" , " site" ) .unwrap ( ) ;
146
client.save_config ( ) .unwrap ( ) ;
147
client.policy_init ( ) .unwrap ( ) ;
148
write ( work.path ( ) , " a.txt" , b " one\n two\n " ) ;
149
client.snapshot ( " t" ) .unwrap ( ) ;
150
client.commit ( " first" , & [ ] ) .unwrap ( ) ;
151
assert! ( client.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
152
let key = client.config.author_key.unwrap ( ) ;
153
let id = client.identity ( & key) .unwrap ( ) ;
154
db:: accounts:: link_identity( & app.db, andy_id, & key.to_hex ( ) , & id.name, & id.email, & id.encode ( ) ) .await.unwrap ( ) ;
155
156
157
let page = send ( app, " GET" , " /andy/demo/edit/main/a.txt" , Some ( & andy) , None ) .await;
158
assert_eq! ( page.status, StatusCode:: OK ) ;
159
assert! ( page.body.contains ( " not delegated" ) , " {}" , page.body) ;
160
assert! ( page.body.contains ( " textarea class=\" code\" " ) ) ;
161
let csrf = csrf_of ( & page.body) ;
162
let r = send (
163
app,
164
" POST" ,
165
" /andy/demo/edit/main" ,
166
Some ( & andy) ,
167
Some ( & format! ( " _csrf={csrf} &path=a.txt&original=a.txt&content=x&message=m" ) ) ,
168
)
169
.await;
170
assert! ( r.body.contains ( " not delegated" ) , " refused: {}" , r.body) ;
171
172
b43a9ab0 Andy McCall 1 month ago ↶
173
client.delegate_add ( app.hub_key.identity.clone ( ) , oxy_core:: identity:: Scope:: CHANGES | oxy_core:: identity:: Scope:: REFS , None ) .unwrap ( ) ;
14530604 Andy McCall 1 month ago ↶
174
assert! ( client.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) , " policy update pushed" ) ;
175
let key2 = client.config.author_key.unwrap ( ) ;
176
let id2 = client.identity ( & key2) .unwrap ( ) ;
b43a9ab0 Andy McCall 1 month ago ↶
177
assert! ( id2.delegate ( & app.hub_key.key) .is_some ( ) ) ;
8933faff Andy McCall 1 month ago ↶
178
179
14530604 Andy McCall 1 month ago ↶
180
db:: accounts:: link_identity( & app.db, andy_id, & key2.to_hex ( ) , & id2.name, & id2.email, & id2.encode ( ) ) .await.unwrap ( ) ;
181
182
183
let r = send (
184
app,
185
" POST" ,
186
" /andy/demo/edit/main" ,
187
Some ( & andy) ,
188
Some ( & format! (
189
" _csrf={csrf} &path=a.txt&original=a.txt&content={} &message={} &mode=commit" ,
190
enc ( " one\n two\n three\n " ) ,
191
enc ( " Add three" )
192
) ) ,
193
)
194
.await;
195
assert_eq! ( r.status, StatusCode:: SEE_OTHER , " {}" , r.body) ;
196
assert_eq! ( r.location.as_deref ( ) , Some ( " /andy/demo/file/main/a.txt" ) ) ;
197
let raw = send ( app, " GET" , " /andy/demo/file/main/a.txt?plain=1" , None , None ) .await;
198
assert_eq! ( raw.body, " one\n two\n three\n " ) ;
199
let hist = send ( app, " GET" , " /andy/demo/changes/main" , Some ( & andy) , None ) .await.body;
200
assert! ( hist.contains ( " Add three" ) ) ;
201
assert! ( send ( app, " GET" , " /andy/demo/pushes" , None , None ) .await.body.contains ( " web edit" ) ) ;
202
let trust = send ( app, " GET" , " /andy/demo/trust" , None , None ) .await.body;
203
assert! ( ! trust.contains ( " INVALID" ) , " every change verifies: {trust}" ) ;
204
205
206
let r = send (
207
app,
208
" POST" ,
209
" /andy/demo/edit/main" ,
210
Some ( & andy) ,
211
Some ( & format! ( " _csrf={csrf} &path=docs/guide.md&content={} &message=Guide&mode=commit" , enc ( " # Guide\n " ) ) ) ,
212
)
213
.await;
214
assert_eq! ( r.location.as_deref ( ) , Some ( " /andy/demo/file/main/docs/guide.md" ) , " {}" , r.body) ;
215
216
217
let csrf_i = csrf_of ( & send ( app, " GET" , " /andy/demo/issues/new" , Some ( & andy) , None ) .await.body) ;
218
send ( app, " POST" , " /andy/demo/issues/new" , Some ( & andy) , Some ( & format! ( " _csrf={csrf_i} &title=Typo&body=" ) ) ) .await;
219
let r = send (
220
app,
221
" POST" ,
222
" /andy/demo/edit/main" ,
223
Some ( & andy) ,
224
Some ( & format! (
225
" _csrf={csrf} &path=docs/guide.md&original=docs/guide.md&content={} &message={} &mode=commit" ,
226
enc ( " # The guide\n " ) ,
227
enc ( " Fix typo, closes #1" )
228
) ) ,
229
)
230
.await;
231
assert_eq! ( r.status, StatusCode:: SEE_OTHER , " {}" , r.body) ;
232
let row = db:: repos:: get( & app.db, " andy" , " demo" ) .await.unwrap ( ) .unwrap ( ) ;
233
assert_eq! ( db:: issues:: get( & app.db, row.id, 1 ) .await.unwrap ( ) .unwrap ( ) .state, " closed" ) ;
234
235
236
let r = send (
237
app,
238
" POST" ,
239
" /andy/demo/edit/main" ,
240
Some ( & andy) ,
241
Some ( & format! (
242
" _csrf={csrf} &path=a.txt&original=a.txt&content={} &message={} &mode=propose&new_bookmark=tweak" ,
243
enc ( " ONE\n two\n three\n " ) ,
244
enc ( " Shout one" )
245
) ) ,
246
)
247
.await;
248
assert_eq! ( r.location.as_deref ( ) , Some ( " /andy/demo/proposal/2" ) , " {}" , r.body) ;
249
let page = send ( app, " GET" , " /andy/demo/proposal/2" , Some ( & andy) , None ) .await;
250
assert! ( page.body.contains ( " ready to land" ) & & page.body.contains ( " >LAND<" ) , " {}" , page.body) ;
0b20f1ec Andy McCall 1 month ago ↶
251
c7067c7b Andy McCall 17 days ago
252
assert! ( page.body.contains ( " /andy/demo/settings/landing\" >landing rules</a> require none." ) , " {}" , page.body) ;
0b20f1ec Andy McCall 1 month ago ↶
253
assert! ( page.body.contains ( " without any approval?" ) , " {}" , page.body) ;
14530604 Andy McCall 1 month ago ↶
254
let r = send ( app, " POST" , " /andy/demo/proposal/2/land" , Some ( & andy) , Some ( & format! ( " _csrf={} " , csrf_of ( & page.body) ) ) ) .await;
a1b98695 Andy McCall 1 month ago ↶
255
256
let back = r.location.clone ( ) .unwrap_or_default ( ) ;
257
assert! ( back.starts_with ( " /andy/demo/proposal/2?" ) & & back.contains ( " notice=Landed." ) , " {back}" ) ;
258
assert! ( send ( app, " GET" , & back, Some ( & andy) , None ) .await.body.contains ( " Landed." ) ) ;
14530604 Andy McCall 1 month ago ↶
259
assert_eq! ( send ( app, " GET" , " /andy/demo/file/main/a.txt?plain=1" , None , None ) .await.body, " ONE\n two\n three\n " ) ;
260
261
262
db:: proposals:: set_requirements(
263
& app.db,
264
row.id,
265
db:: proposals:: Requirements { required_approvals: 0 , require_owners: false , allow_conflicts: true } ,
266
)
267
.await
268
.unwrap ( ) ;
269
for ( b, text) in [ ( " left" , " Left\n two\n three\n " ) , ( " right" , " Right\n two\n three\n " ) ] {
270
let r = send (
271
app,
272
" POST" ,
273
" /andy/demo/edit/main" ,
274
Some ( & andy) ,
275
Some ( & format! ( " _csrf={csrf} &path=a.txt&original=a.txt&content={} &message={b} &mode=propose&new_bookmark={b} " , enc ( text) ) ) ,
276
)
277
.await;
278
assert! ( r.location.as_deref ( ) .is_some_and ( | l | l.contains ( " /proposal/" ) ) , " {}" , r.body) ;
279
}
280
for n in [ 3 , 4 ] {
281
let page = send ( app, " GET" , & format! ( " /andy/demo/proposal/{n} " ) , Some ( & andy) , None ) .await;
282
let r =
283
send ( app, " POST" , & format! ( " /andy/demo/proposal/{n} /land" ) , Some ( & andy) , Some ( & format! ( " _csrf={} " , csrf_of ( & page.body) ) ) ) .await;
a1b98695 Andy McCall 1 month ago ↶
284
assert! ( r.location.as_deref ( ) .is_some_and ( | l | l.contains ( " notice=Landed." ) ) , " #{n}: {:?}" , r.location) ;
14530604 Andy McCall 1 month ago ↶
285
}
286
let conflicts = send ( app, " GET" , " /andy/demo/conflicts" , Some ( & andy) , None ) .await.body;
c5f3bde4 Andy McCall 1 month ago ↶
287
assert! ( conflicts.contains ( " resolve region by region" ) & & conflicts.contains ( " /andy/demo/resolve/main/a.txt" ) , " {conflicts}" ) ;
14530604 Andy McCall 1 month ago ↶
288
let page = send ( app, " GET" , " /andy/demo/edit/main/a.txt" , Some ( & andy) , None ) .await;
289
assert! ( page.body.contains ( " Resolve a.txt" ) & & page.body.contains ( " <<<<<<< oxy conflict" ) , " {}" , page.body) ;
8a2dbcb5 Andy McCall 27 days ago ↶
290
291
292
let blamed = send ( app, " GET" , " /andy/demo/file/main/a.txt?blame=1" , Some ( & andy) , None ) .await;
293
assert_eq! ( blamed.status, 200 , " {}" , blamed.body) ;
294
assert! ( blamed.body.contains ( " oxy conflict" ) & & blamed.body.contains ( " class=\" blame\" ></td>" ) , " {}" , blamed.body) ;
13b27cb2 Andy McCall 18 days ago ↶
295
296
297
let raw = send ( app, " GET" , " /andy/demo/raw/main/a.txt" , Some ( & andy) , None ) .await.body;
298
let marker = raw.lines ( ) .find ( | l | l.starts_with ( " <<<<<<< oxy conflict" ) ) .expect ( " a marker" ) .to_string ( ) ;
14530604 Andy McCall 1 month ago ↶
299
let r = send (
300
app,
301
" POST" ,
302
" /andy/demo/edit/main" ,
303
Some ( & andy) ,
13b27cb2 Andy McCall 18 days ago ↶
304
Some ( & format! ( " _csrf={csrf} &path=a.txt&original=a.txt&content={} &message=half&mode=commit" , enc ( & format! ( " {marker} \n Left\n " ) ) ) ) ,
14530604 Andy McCall 1 month ago ↶
305
)
306
.await;
307
assert_ne! ( r.status, StatusCode:: SEE_OTHER ) ;
c5f3bde4 Andy McCall 1 month ago ↶
308
309
let page = send ( app, " GET" , " /andy/demo/resolve/main/a.txt" , Some ( & andy) , None ) .await;
310
assert! (
311
page.body.contains ( " id=\" region-1\" " ) & & page.body.contains ( " keep side 2" ) & & page.body.contains ( " keep every side" ) ,
312
" {}" ,
313
page.body
314
) ;
16cb2b3a Andy McCall 1 month ago ↶
315
assert! (
c96b3286 Andy McCall 1 month ago ↶
316
page.body.contains ( " vscode://oxygenhub.oxygen-vscode/resolve?url=" ) & & page.body.contains ( " &bookmark=main&path=a.txt" ) ,
16cb2b3a Andy McCall 1 month ago ↶
317
" {}" ,
318
page.body
319
) ;
c5f3bde4 Andy McCall 1 month ago ↶
320
let state = {
321
let i = page.body.find ( " name=\" state\" value=\" " ) .unwrap ( ) + " name=\" state\" value=\" " .len ( ) ;
322
page.body[ i.. i + 64 ] .to_string ( )
323
} ;
5e6d6484 Andy McCall 1 month ago ↶
324
325
let live_req = Request:: builder( )
326
.uri ( " /andy/demo/resolve-live/main/a.txt?client=tab-a" )
327
.header ( header:: COOKIE , format! ( " oxyhub={andy} " ) )
328
.body ( Body:: empty( ) )
329
.unwrap ( ) ;
330
let res = oxygenhub:: router( app.clone ( ) ) .oneshot ( live_req) .await.unwrap ( ) ;
331
assert_eq! ( res.status ( ) , StatusCode:: OK ) ;
332
let mut stream = res.into_body ( ) ;
333
let snapshot = next_event ( & mut stream, " snapshot" ) .await;
334
assert! ( snapshot.contains ( " \" present\" :[\" andy\" ]" ) , " {snapshot}" ) ;
335
let choose = | region : & str , choice : & str | {
336
let body = serde_json:: json! ( { " _csrf" : csrf, " client" : " tab-b" , " region" : region, " choice" : choice, " text" : " " } ) .to_string ( ) ;
337
Request:: builder( )
338
.method ( " POST" )
339
.uri ( " /andy/demo/resolve-live/main/a.txt" )
340
.header ( header:: COOKIE , format! ( " oxyhub={andy} " ) )
341
.header ( header:: CONTENT_TYPE , " application/json" )
342
.body ( Body:: from( body) )
343
.unwrap ( )
344
} ;
345
let r = oxygenhub:: router( app.clone ( ) ) .oneshot ( choose ( " 1" , " side2" ) ) .await.unwrap ( ) ;
346
assert_eq! ( r.status ( ) , StatusCode:: NO_CONTENT ) ;
347
let shared = next_event ( & mut stream, " choice" ) .await;
348
assert! ( shared.contains ( " \" side2\" " ) & & shared.contains ( " \" tab-b\" " ) , " {shared}" ) ;
349
assert_ne! ( oxygenhub:: router( app.clone ( ) ) .oneshot ( choose ( " 1" , " rm -rf" ) ) .await.unwrap ( ) .status ( ) , StatusCode:: NO_CONTENT ) ;
350
c5f3bde4 Andy McCall 1 month ago ↶
351
let stale = send (
352
app,
353
" POST" ,
354
" /andy/demo/resolve/main/a.txt" ,
355
Some ( & andy) ,
356
Some ( & format! ( " _csrf={csrf} &state={} &r1=side2&message=x" , " 0" .repeat ( 64 ) ) ) ,
357
)
358
.await;
359
assert! ( stale.body.contains ( " has moved since this page was loaded" ) , " {}" , stale.body) ;
360
let unchosen =
361
send ( app, " POST" , " /andy/demo/resolve/main/a.txt" , Some ( & andy) , Some ( & format! ( " _csrf={csrf} &state={state} &message=x" ) ) ) .await;
362
assert! ( unchosen.body.contains ( " choose how to resolve conflict 1" ) , " {}" , unchosen.body) ;
14530604 Andy McCall 1 month ago ↶
363
let r = send (
364
app,
365
" POST" ,
c5f3bde4 Andy McCall 1 month ago ↶
366
" /andy/demo/resolve/main/a.txt" ,
14530604 Andy McCall 1 month ago ↶
367
Some ( & andy) ,
c5f3bde4 Andy McCall 1 month ago ↶
368
Some ( & format! ( " _csrf={csrf} &state={state} &r1=text&t1={} &message={} " , enc ( " Both" ) , enc ( " Resolve a.txt" ) ) ) ,
14530604 Andy McCall 1 month ago ↶
369
)
370
.await;
371
assert_eq! ( r.status, StatusCode:: SEE_OTHER , " {}" , r.body) ;
c5f3bde4 Andy McCall 1 month ago ↶
372
let change = r.location.unwrap ( ) ;
373
assert! ( change.contains ( " /andy/demo/change/" ) , " {change}" ) ;
5e6d6484 Andy McCall 1 month ago ↶
374
let landed = next_event ( & mut stream, " landed" ) .await;
375
assert! ( landed.contains ( & change) , " the other tab hears the resolution land: {landed}" ) ;
376
drop ( stream) ;
14530604 Andy McCall 1 month ago ↶
377
assert! ( send ( app, " GET" , " /andy/demo/conflicts" , None , None ) .await.body.contains ( " No bookmark carries a conflict" ) ) ;
378
assert_eq! ( send ( app, " GET" , " /andy/demo/file/main/a.txt?plain=1" , None , None ) .await.body, " Both\n two\n three\n " ) ;
c5f3bde4 Andy McCall 1 month ago ↶
379
let shown = send ( app, " GET" , & change, Some ( & andy) , None ) .await.body;
380
assert! ( shown.to_lowercase ( ) .contains ( " resolution" ) , " a resolution change" ) ;
14530604 Andy McCall 1 month ago ↶
381
fd958fbc Andy McCall 1 month ago ↶
382
383
for ( b, text) in [ ( " first-new" , " first\n " ) , ( " second-new" , " second\n " ) ] {
384
let r = send (
385
app,
386
" POST" ,
387
" /andy/demo/edit/main" ,
388
Some ( & andy) ,
389
Some ( & format! ( " _csrf={csrf} &path=new.txt&content={} &message={b} &mode=propose&new_bookmark={b} " , enc ( text) ) ) ,
390
)
391
.await;
392
assert! ( r.location.as_deref ( ) .is_some_and ( | l | l.contains ( " /proposal/" ) ) , " {}" , r.body) ;
393
}
394
for n in [ 5 , 6 ] {
395
let page = send ( app, " GET" , & format! ( " /andy/demo/proposal/{n} " ) , Some ( & andy) , None ) .await;
396
let r =
397
send ( app, " POST" , & format! ( " /andy/demo/proposal/{n} /land" ) , Some ( & andy) , Some ( & format! ( " _csrf={} " , csrf_of ( & page.body) ) ) ) .await;
a1b98695 Andy McCall 1 month ago ↶
398
assert! ( r.location.as_deref ( ) .is_some_and ( | l | l.contains ( " notice=Landed." ) ) , " #{n}: {:?} {}" , r.location, r.body) ;
fd958fbc Andy McCall 1 month ago ↶
399
}
400
let conflicts = send ( app, " GET" , " /andy/demo/conflicts" , Some ( & andy) , None ) .await.body;
401
assert! ( conflicts.contains ( " choose which to keep" ) & & conflicts.contains ( " /andy/demo/resolve/main/new.txt" ) , " {conflicts}" ) ;
402
let page = send ( app, " GET" , " /andy/demo/resolve/main/new.txt" , Some ( & andy) , None ) .await;
403
assert! ( page.body.contains ( " new.txt.oxy-conflict-1" ) & & page.body.contains ( " new.txt.oxy-conflict-2" ) , " {}" , page.body) ;
404
let state = {
405
let i = page.body.find ( " name=\" state\" value=\" " ) .unwrap ( ) + " name=\" state\" value=\" " .len ( ) ;
406
page.body[ i.. i + 64 ] .to_string ( )
407
} ;
408
16cb2b3a Andy McCall 1 month ago ↶
409
let preview = | text : & str | page.body.find ( & format! ( " <pre class=\" marked\" >{text} " ) ) .expect ( " a candidate's preview" ) ;
410
let second = if preview ( " second" ) < preview ( " first" ) { 0 } else { 1 } ;
fd958fbc Andy McCall 1 month ago ↶
411
let r = send (
412
app,
413
" POST" ,
414
" /andy/demo/resolve/main/new.txt" ,
415
Some ( & andy) ,
416
Some ( & format! ( " _csrf={csrf} &state={state} &keep={second} &message={} " , enc ( " Keep the second new.txt" ) ) ) ,
417
)
418
.await;
419
assert_eq! ( r.status, StatusCode:: SEE_OTHER , " {}" , r.body) ;
420
421
let hash = r.location.unwrap ( ) .rsplit ( ' /' ) .next ( ) .unwrap ( ) .to_string ( ) ;
422
let detail = send ( app, " GET" , & format! ( " /api/v1/repos/andy/demo/changes/{hash} " ) , Some ( & andy) , None ) .await.body;
423
assert! ( detail.contains ( " \" kind\" :\" renamed\" " ) , " {detail}" ) ;
424
assert! ( send ( app, " GET" , " /andy/demo/conflicts" , None , None ) .await.body.contains ( " No bookmark carries a conflict" ) ) ;
425
assert_eq! ( send ( app, " GET" , " /andy/demo/file/main/new.txt?plain=1" , None , None ) .await.body, " second\n " ) ;
ffb67a81 Andy McCall 23 days ago
426
let tree = send ( app, " GET" , " /andy/demo?at=main" , None , None ) .await.body;
fd958fbc Andy McCall 1 month ago ↶
427
assert! ( ! tree.contains ( " oxy-conflict" ) , " the candidates are gone" ) ;
428
9e5ef6c2 Andy McCall 1 month ago ↶
429
430
431
let r = send (
432
app,
433
" POST" ,
434
" /andy/demo/edit/main" ,
435
Some ( & andy) ,
436
Some ( & format! ( " _csrf={csrf} &path=img.bin&content=base%00&message=img&mode=commit" ) ) ,
437
)
438
.await;
439
assert_eq! ( r.status, StatusCode:: SEE_OTHER , " {}" , r.body) ;
440
for ( b, text) in [ ( " img-a" , " alpha%00" ) , ( " img-b" , " beta%00" ) ] {
441
let r = send (
442
app,
443
" POST" ,
444
" /andy/demo/edit/main" ,
445
Some ( & andy) ,
446
Some ( & format! ( " _csrf={csrf} &path=img.bin&original=img.bin&content={text} &message={b} &mode=propose&new_bookmark={b} " ) ) ,
447
)
448
.await;
449
assert! ( r.location.as_deref ( ) .is_some_and ( | l | l.contains ( " /proposal/" ) ) , " {}" , r.body) ;
450
}
451
for n in [ 7 , 8 ] {
452
let page = send ( app, " GET" , & format! ( " /andy/demo/proposal/{n} " ) , Some ( & andy) , None ) .await;
453
let r =
454
send ( app, " POST" , & format! ( " /andy/demo/proposal/{n} /land" ) , Some ( & andy) , Some ( & format! ( " _csrf={} " , csrf_of ( & page.body) ) ) ) .await;
a1b98695 Andy McCall 1 month ago ↶
455
assert! ( r.location.as_deref ( ) .is_some_and ( | l | l.contains ( " notice=Landed." ) ) , " #{n}: {:?} {}" , r.location, r.body) ;
9e5ef6c2 Andy McCall 1 month ago ↶
456
}
457
let conflicts = send ( app, " GET" , " /andy/demo/conflicts" , Some ( & andy) , None ) .await.body;
458
assert! ( conflicts.contains ( " replace (2 candidates)" ) & & conflicts.contains ( " /andy/demo/resolve/main/img.bin" ) , " {conflicts}" ) ;
459
let page = send ( app, " GET" , " /andy/demo/resolve/main/img.bin" , Some ( & andy) , None ) .await;
460
assert! ( page.body.contains ( " has 2 versions" ) & & page.body.contains ( " version 2 from change" ) , " {}" , page.body) ;
461
let state = {
462
let i = page.body.find ( " name=\" state\" value=\" " ) .unwrap ( ) + " name=\" state\" value=\" " .len ( ) ;
463
page.body[ i.. i + 64 ] .to_string ( )
464
} ;
465
let version = | text : & str | page.body.find ( & format! ( " <pre class=\" marked\" >{text} " ) ) .expect ( " a version's preview" ) ;
466
let beta = if version ( " beta" ) < version ( " alpha" ) { 0 } else { 1 } ;
467
let r = send (
468
app,
469
" POST" ,
470
" /andy/demo/resolve/main/img.bin" ,
471
Some ( & andy) ,
472
Some ( & format! ( " _csrf={csrf} &state={state} &keep={beta} &message={} " , enc ( " Keep beta" ) ) ) ,
473
)
474
.await;
475
assert_eq! ( r.status, StatusCode:: SEE_OTHER , " {}" , r.body) ;
476
assert! ( send ( app, " GET" , " /andy/demo/conflicts" , None , None ) .await.body.contains ( " No bookmark carries a conflict" ) ) ;
477
assert_eq! ( send ( app, " GET" , " /andy/demo/raw/main/img.bin" , None , None ) .await.body, " beta\u{0} " ) ;
478
let change = send ( app, " GET" , r.location.as_deref ( ) .unwrap ( ) , Some ( & andy) , None ) .await.body;
479
assert! ( change.contains ( " >resolution<" ) , " a resolution change" ) ;
480
14530604 Andy McCall 1 month ago ↶
481
482
let r = send (
483
app,
484
" POST" ,
485
" /andy/demo/edit/main" ,
486
Some ( & andy) ,
487
Some ( & format! ( " _csrf={csrf} &path=docs/guide.md&original=docs/guide.md&message=Drop&delete=1" ) ) ,
488
)
489
.await;
490
assert_eq! ( r.status, StatusCode:: SEE_OTHER , " {}" , r.body) ;
491
assert_eq! ( send ( app, " GET" , " /andy/demo/file/main/docs/guide.md" , None , None ) .await.status, StatusCode:: NOT_FOUND ) ;
492
dd0788b7 Andy McCall 11 days ago ↶
493
494
495
496
497
db:: proposals:: set_requirements(
498
& app.db,
499
row.id,
500
db:: proposals:: Requirements { required_approvals: 0 , require_owners: false , allow_conflicts: false } ,
501
)
502
.await
503
.unwrap ( ) ;
504
for ( path, text) in [ ( " .oxyattributes" , " CHANGELOG.md merge=union\n " ) , ( " CHANGELOG.md" , " # Changelog\n ## Unreleased\n ## 1.0\n " ) ] {
505
let r = send (
506
app,
507
" POST" ,
508
" /andy/demo/edit/main" ,
509
Some ( & andy) ,
510
Some ( & format! ( " _csrf={csrf} &path={} &content={} &message=add&mode=commit" , enc ( path) , enc ( text) ) ) ,
511
)
512
.await;
513
assert_eq! ( r.status, StatusCode:: SEE_OTHER , " {}" , r.body) ;
514
}
515
for ( b, line) in [ ( " log-a" , " - first\n " ) , ( " log-b" , " - second\n " ) ] {
516
let text = format! ( " # Changelog\n ## Unreleased\n {line} ## 1.0\n " ) ;
517
let r = send (
518
app,
519
" POST" ,
520
" /andy/demo/edit/main" ,
521
Some ( & andy) ,
522
Some ( & format! (
523
" _csrf={csrf} &path=CHANGELOG.md&original=CHANGELOG.md&content={} &message={b} &mode=propose&new_bookmark={b} " ,
524
enc ( & text)
525
) ) ,
526
)
527
.await;
528
assert! ( r.location.as_deref ( ) .is_some_and ( | l | l.contains ( " /proposal/" ) ) , " {}" , r.body) ;
529
}
530
for n in [ 9 , 10 ] {
531
let page = send ( app, " GET" , & format! ( " /andy/demo/proposal/{n} " ) , Some ( & andy) , None ) .await;
532
assert! ( ! page.body.contains ( " landing would carry conflicts" ) , " #{n}: {}" , page.body) ;
533
let r =
534
send ( app, " POST" , & format! ( " /andy/demo/proposal/{n} /land" ) , Some ( & andy) , Some ( & format! ( " _csrf={} " , csrf_of ( & page.body) ) ) ) .await;
535
assert! ( r.location.as_deref ( ) .is_some_and ( | l | l.contains ( " notice=Landed." ) ) , " #{n}: {:?} {}" , r.location, r.body) ;
536
}
537
assert_eq! (
538
send ( app, " GET" , " /andy/demo/raw/main/CHANGELOG.md" , None , None ) .await.body,
539
" # Changelog\n ## Unreleased\n - first\n - second\n ## 1.0\n "
540
) ;
541
assert! ( send ( app, " GET" , " /andy/demo/conflicts" , None , None ) .await.body.contains ( " No bookmark carries a conflict" ) ) ;
542
assert! ( send ( app, " GET" , " /andy/demo/changes/main" , Some ( & andy) , None ) .await.body.contains ( " Keep both sides in CHANGELOG.md" ) ) ;
543
14530604 Andy McCall 1 month ago ↶
544
545
client.pull ( " origin" ) .unwrap ( ) ;
546
let v: Vec< _ > =
547
client.log ( ) .unwrap ( ) .into_iter ( ) .filter ( | e | ! e.is_working_copy) .map ( | e | client.authenticate_change ( & e.hash) .unwrap ( ) ) .collect ( ) ;
548
assert! ( v.iter ( ) .all ( | s | matches! ( s, oxy_core:: repo:: SignatureStatus:: Valid { .. } ) ) , " {v:?}" ) ;
549
}
8933faff Andy McCall 1 month ago ↶
550
551
552
553
554
555
# [ tokio ::test ( flavor = " multi_thread" ) ]
556
async fn a_rotated_identity_links_itself_and_an_agent_s_change_says_via ( ) {
557
hermetic ( ) ;
558
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
559
let mut config = Config:: load( dir.path ( ) ) .unwrap ( ) ;
c8684d44 Andy McCall 20 days ago
560
561
config.registration.setup_code = " test-setup" .into ( ) ;
8933faff Andy McCall 1 month ago ↶
562
config.auth.push_tokens = vec! [ " site" .into ( ) ] ;
563
config.registration.mode = " open" .into ( ) ;
564
let listener = tokio:: net:: TcpListener:: bind( " 127.0.0.1:0" ) .await.unwrap ( ) ;
565
let port = listener.local_addr ( ) .unwrap ( ) .port ( ) ;
566
config.server.base_url = format! ( " http://127.0.0.1:{port} " ) ;
567
let app = oxygenhub:: build( config) .await.unwrap ( ) ;
568
let router = oxygenhub:: router( app.clone ( ) ) ;
569
tokio:: spawn( async move { axum:: serve( listener, router) .await.unwrap ( ) } ) ;
570
let base = format! ( " http://127.0.0.1:{port} " ) ;
571
let app = & app;
c8684d44 Andy McCall 20 days ago
572
let r =
573
send ( app, " POST" , " /register" , None , Some ( " username=andy&email=a%40example.com&password=correct-horse-battery&setup=test-setup" ) )
574
.await;
8933faff Andy McCall 1 month ago ↶
575
assert! ( r.cookie.is_some ( ) ) ;
576
let andy_id = db:: accounts:: account_by_name( & app.db, " andy" ) .await.unwrap ( ) .unwrap ( ) .id;
577
let repo = RepoName:: parse( " andy/demo" ) .unwrap ( ) ;
578
app.repos.create ( & repo, None ) .unwrap ( ) ;
579
db:: repos:: create( & app.db, " andy" , " demo" , " " , hub_api:: Visibility:: Public) .await.unwrap ( ) ;
580
let work = tempfile:: tempdir( ) .unwrap ( ) ;
581
let home = tempfile:: tempdir( ) .unwrap ( ) ;
582
let mut client = Repo:: init( work.path ( ) ) .unwrap ( ) .with_home ( home.path ( ) ) ;
583
client.create_identity ( " Andy" , " [email protected] " , IdentityScope:: Repo) .unwrap ( ) ;
584
client.config.set ( " core.sign" , " change" ) .unwrap ( ) ;
585
client.config.set ( " remote.origin.url" , & format! ( " {base} /andy/demo" ) ) .unwrap ( ) ;
586
client.config.set ( " remote.origin.token" , " site" ) .unwrap ( ) ;
587
client.save_config ( ) .unwrap ( ) ;
588
write ( work.path ( ) , " a.txt" , b " one\n " ) ;
589
client.snapshot ( " t" ) .unwrap ( ) ;
590
client.commit ( " first" , & [ ] ) .unwrap ( ) ;
591
assert! ( client.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
592
let key1 = client.config.author_key.unwrap ( ) ;
593
let id1 = client.identity ( & key1) .unwrap ( ) ;
594
db:: accounts:: link_identity( & app.db, andy_id, & key1.to_hex ( ) , & id1.name, & id1.email, & id1.encode ( ) ) .await.unwrap ( ) ;
595
596
597
let agent_home = tempfile:: tempdir( ) .unwrap ( ) ;
598
let mut agent_repo = Repo:: init( & work.path ( ) .join ( " agent-scratch" ) ) .unwrap ( ) .with_home ( agent_home.path ( ) ) ;
599
let ( agent_key, agent_id) = agent_repo.create_identity ( " Antigravity" , " [email protected] " , IdentityScope:: User) .unwrap ( ) ;
600
client.delegate_add ( agent_id, oxy_core:: identity:: Scope:: CHANGES , None ) .unwrap ( ) ;
601
let key2 = client.config.author_key.unwrap ( ) ;
602
assert_ne! ( key1, key2) ;
603
write ( work.path ( ) , " a.txt" , b " one\n two\n " ) ;
604
client.snapshot ( " t" ) .unwrap ( ) ;
605
let by_andy = client.commit ( " second, by Andy" , & [ ] ) .unwrap ( ) ;
606
607
let mut as_agent = Repo:: open( work.path ( ) ) .unwrap ( ) .with_home ( agent_home.path ( ) ) ;
608
as_agent.set_user_config ( " author.signer" , & agent_key.to_hex ( ) ) .unwrap ( ) ;
609
as_agent.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
610
write ( work.path ( ) , " b.txt" , b " by the agent\n " ) ;
611
as_agent.snapshot ( " t" ) .unwrap ( ) ;
612
let by_agent = as_agent.commit ( " third, by the agent" , & [ ] ) .unwrap ( ) ;
7c06f5b2 Andy McCall 19 days ago ↶
613
614
615
client.refresh ( ) .unwrap ( ) ;
8933faff Andy McCall 1 month ago ↶
616
assert! ( client.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
617
tokio:: time:: sleep( std:: time:: Duration:: from_millis( 300 ) ) .await;
618
619
620
let linked = db:: accounts:: identity_by_key( & app.db, & key2.to_hex ( ) ) .await.unwrap ( ) ;
621
assert_eq! ( linked.map ( | l | l.account_id ) , Some ( andy_id) , " the rotated identity links itself" ) ;
622
assert! ( db:: accounts:: identity_by_key( & app.db, & agent_key.to_hex ( ) ) .await.unwrap ( ) .is_none ( ) , " an agent's key is nobody's account" ) ;
623
624
625
let page = send ( app, " GET" , & format! ( " /andy/demo/change/{} " , by_agent.to_hex ( ) ) , None , None ) .await.body;
626
assert! ( page.contains ( " <dt>via</dt>" ) , " {page}" ) ;
627
assert! ( page.contains ( " Antigravity" ) & & page.contains ( " delegate for changes" ) , " {page}" ) ;
628
assert! ( page.contains ( " href=\" /andy\" " ) , " the author is Andy's account: {page}" ) ;
629
let andy_page = send ( app, " GET" , & format! ( " /andy/demo/change/{} " , by_andy.to_hex ( ) ) , None , None ) .await.body;
630
assert! ( ! andy_page.contains ( " <dt>via</dt>" ) , " {andy_page}" ) ;
631
632
let rows = send ( app, " GET" , " /andy/demo/changes/main" , None , None ) .await.body;
633
assert! ( rows.contains ( " via Antigravity" ) , " {rows}" ) ;
634
let hist = send ( app, " GET" , " /api/v1/repos/andy/demo/history/main" , None , None ) .await.body;
635
let v: serde_json:: Value = serde_json:: from_str( & hist) .unwrap ( ) ;
636
let agent_change = v[ " changes" ] .as_array ( ) .unwrap ( ) .iter ( ) .find ( | c | c[ " hash" ] = = by_agent.to_hex ( ) ) .unwrap ( ) ;
637
assert_eq! ( agent_change[ " via" ] [ " name" ] , " Antigravity" ) ;
638
assert_eq! ( agent_change[ " via" ] [ " scope" ] , " changes" ) ;
639
assert_eq! ( agent_change[ " author" ] [ " account" ] , " andy" ) ;
640
641
let overview = send ( app, " GET" , " /andy/demo" , None , None ) .await.body;
206c1fc1 Andy McCall 1 month ago ↶
642
assert! ( overview.contains ( " /avatar/agent/Antigravity" ) & & overview.contains ( " Antigravity <span class=\" mute small\" title=\" changes signed by this agent for them\" >· 1</span>" ) , " {overview}" ) ;
643
assert_eq! ( overview.matches ( " class=\" avatar sm\" src=\" /avatar/andy\" " ) .count ( ) , 1 , " one row for Andy, whatever his keys" ) ;
644
let mark = send ( app, " GET" , " /avatar/agent/Claude%20Code" , None , None ) .await;
645
assert! ( mark.body.contains ( " <title>Claude</title>" ) , " a known agent gets its brand mark" ) ;
646
assert! ( send ( app, " GET" , " /avatar/agent/HAL" , None , None ) .await.body.contains ( " <svg" ) , " an unknown one its identicon" ) ;
8933faff Andy McCall 1 month ago ↶
647
648
let badge = send ( app, " GET" , " /andy/demo/flow/badge.svg" , None , None ) .await;
649
assert_eq! ( badge.status, StatusCode:: OK ) ;
650
assert! ( badge.body.contains ( " <svg" ) & & badge.body.contains ( " no runs" ) , " {}" , badge.body) ;
651
}
d0fbea2e Andy McCall 1 month ago ↶
652
653
# [ tokio ::test ( flavor = " multi_thread" ) ]
654
async fn a_repository_is_transferred_and_archived_from_advanced_settings ( ) {
655
hermetic ( ) ;
656
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
657
let mut config = Config:: load( dir.path ( ) ) .unwrap ( ) ;
c8684d44 Andy McCall 20 days ago
658
659
config.registration.setup_code = " test-setup" .into ( ) ;
d0fbea2e Andy McCall 1 month ago ↶
660
config.auth.push_tokens = vec! [ " site" .into ( ) ] ;
661
config.registration.mode = " open" .into ( ) ;
662
let listener = tokio:: net:: TcpListener:: bind( " 127.0.0.1:0" ) .await.unwrap ( ) ;
663
let port = listener.local_addr ( ) .unwrap ( ) .port ( ) ;
664
config.server.base_url = format! ( " http://127.0.0.1:{port} " ) ;
665
let app = oxygenhub:: build( config) .await.unwrap ( ) ;
666
let router = oxygenhub:: router( app.clone ( ) ) ;
667
tokio:: spawn( async move { axum:: serve( listener, router) .await.unwrap ( ) } ) ;
668
let base = format! ( " http://127.0.0.1:{port} " ) ;
669
let app = & app;
c8684d44 Andy McCall 20 days ago
670
let r =
671
send ( app, " POST" , " /register" , None , Some ( " username=andy&email=a%40example.com&password=correct-horse-battery&setup=test-setup" ) )
672
.await;
d0fbea2e Andy McCall 1 month ago ↶
673
let andy = r.cookie.unwrap ( ) ;
674
let andy_id = db:: accounts:: account_by_name( & app.db, " andy" ) .await.unwrap ( ) .unwrap ( ) .id;
675
db:: accounts:: create_org( & app.db, " doomsday" , " Doomsday One" , andy_id) .await.unwrap ( ) ;
676
let repo = RepoName:: parse( " andy/vk" ) .unwrap ( ) ;
677
app.repos.create ( & repo, None ) .unwrap ( ) ;
678
db:: repos:: create( & app.db, " andy" , " vk" , " a port" , hub_api:: Visibility:: Public) .await.unwrap ( ) ;
679
let work = tempfile:: tempdir( ) .unwrap ( ) ;
680
let mut client = Repo:: init( work.path ( ) ) .unwrap ( ) ;
681
client.create_identity ( " Andy McCall" , " [email protected] " , IdentityScope:: Repo) .unwrap ( ) ;
682
client.config.set ( " core.sign" , " change" ) .unwrap ( ) ;
683
client.config.set ( " remote.origin.url" , & format! ( " {base} /andy/vk" ) ) .unwrap ( ) ;
684
client.config.set ( " remote.origin.token" , " site" ) .unwrap ( ) ;
685
client.save_config ( ) .unwrap ( ) ;
686
write ( work.path ( ) , " a.txt" , b " one\n " ) ;
687
client.snapshot ( " t" ) .unwrap ( ) ;
688
client.commit ( " first" , & [ ] ) .unwrap ( ) ;
689
assert! ( client.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
690
691
c7067c7b Andy McCall 17 days ago
692
let settings = send ( app, " GET" , " /andy/vk/settings/advanced" , Some ( & andy) , None ) .await.body;
d0fbea2e Andy McCall 1 month ago ↶
693
assert! (
694
settings.contains ( " Advanced" ) & & settings.contains ( " Transfer ownership" ) & & settings.contains ( " Delete wiki data" ) ,
695
" {settings}"
696
) ;
697
let csrf = csrf_of ( & settings) ;
698
let r = send ( app, " POST" , " /andy/vk/settings/transfer" , Some ( & andy) , Some ( & format! ( " _csrf={csrf} &owner=doomsday&confirm=wrong" ) ) ) .await;
c7067c7b Andy McCall 17 days ago
699
let r = settled ( app, r, Some ( & andy) , " /andy/vk/settings/advanced" ) .await;
d0fbea2e Andy McCall 1 month ago ↶
700
assert! ( r.body.contains ( " full name to confirm" ) , " {}" , r.body) ;
701
let r =
702
send ( app, " POST" , " /andy/vk/settings/transfer" , Some ( & andy) , Some ( & format! ( " _csrf={csrf} &owner=nobody&confirm=andy%2Fvk" ) ) ) .await;
c7067c7b Andy McCall 17 days ago
703
let r = settled ( app, r, Some ( & andy) , " /andy/vk/settings/advanced" ) .await;
d0fbea2e Andy McCall 1 month ago ↶
704
assert! ( r.body.contains ( " no user or organisation" ) , " {}" , r.body) ;
705
706
707
let r =
708
send ( app, " POST" , " /andy/vk/settings/transfer" , Some ( & andy) , Some ( & format! ( " _csrf={csrf} &owner=doomsday&confirm=andy%2Fvk" ) ) ) .await;
c7067c7b Andy McCall 17 days ago
709
let moved = settled ( app, r, Some ( & andy) , " /doomsday/vk/settings/advanced" ) .await;
710
assert! ( moved.body.contains ( " Moved from andy/vk" ) , " {}" , moved.body) ;
d0fbea2e Andy McCall 1 month ago ↶
711
let page = send ( app, " GET" , " /doomsday/vk" , None , None ) .await;
712
assert_eq! ( page.status, StatusCode:: OK ) ;
713
assert! ( page.body.contains ( " a port" ) , " {}" , page.body) ;
714
let old = send ( app, " GET" , " /andy/vk/changes/main" , None , None ) .await;
715
assert_eq! ( old.status, StatusCode:: SEE_OTHER , " {}" , old.body) ;
716
assert! ( app.repos.exists ( & RepoName:: parse( " doomsday/vk" ) .unwrap ( ) ) & & ! app.repos.exists ( & repo) ) ;
717
write ( work.path ( ) , " a.txt" , b " one\n two\n " ) ;
718
client.snapshot ( " t" ) .unwrap ( ) ;
719
client.commit ( " second" , & [ ] ) .unwrap ( ) ;
720
assert! ( client.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) , " the old remote address still resolves" ) ;
721
let audit = db:: accounts:: audit_rows( & app.db, 10 ) .await.unwrap ( ) ;
722
assert! ( audit.iter ( ) .any ( | a | a.action = = " repo.transferred" & & a.target = = " andy/vk" & & a.detail = = " doomsday/vk" ) ) ;
723
724
c7067c7b Andy McCall 17 days ago
725
let settings = send ( app, " GET" , " /doomsday/vk/settings/advanced" , Some ( & andy) , None ) .await.body;
d0fbea2e Andy McCall 1 month ago ↶
726
let csrf = csrf_of ( & settings) ;
727
let r =
728
send ( app, " POST" , " /doomsday/vk/settings/archive" , Some ( & andy) , Some ( & format! ( " _csrf={csrf} &action=archive&reason=Superseded" ) ) )
729
.await;
c7067c7b Andy McCall 17 days ago
730
let r = settled ( app, r, Some ( & andy) , " /doomsday/vk/settings/advanced" ) .await;
d0fbea2e Andy McCall 1 month ago ↶
731
assert! ( r.body.contains ( " is archived" ) & & r.body.contains ( " Bring this repository back" ) & & r.body.contains ( " Superseded" ) , " {}" , r.body) ;
732
assert! ( send ( app, " GET" , " /doomsday/vk" , None , None ) .await.body.contains ( " >archived<" ) ) ;
733
write ( work.path ( ) , " a.txt" , b " one\n two\n three\n " ) ;
734
client.snapshot ( " t" ) .unwrap ( ) ;
735
client.commit ( " third" , & [ ] ) .unwrap ( ) ;
736
let pushed = client.push ( " origin" , false ) ;
737
assert! ( pushed.is_err ( ) | | ! pushed.unwrap ( ) .iter ( ) .all ( | r | r.ok ) , " an archived repository takes no pushes" ) ;
738
let r = send ( app, " POST" , " /doomsday/vk/issues/new" , Some ( & andy) , Some ( & format! ( " _csrf={csrf} &title=x&body=y" ) ) ) .await;
739
assert! ( r.body.contains ( " archived and read-only" ) , " {} {}" , r.status, r.body) ;
740
let r = send ( app, " POST" , " /doomsday/vk/settings/archive" , Some ( & andy) , Some ( & format! ( " _csrf={csrf} &action=unarchive" ) ) ) .await;
c7067c7b Andy McCall 17 days ago
741
let r = settled ( app, r, Some ( & andy) , " /doomsday/vk/settings/advanced" ) .await;
d0fbea2e Andy McCall 1 month ago ↶
742
assert! ( r.body.contains ( " The repository is back" ) , " {}" , r.body) ;
743
assert! ( client.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
744
}
66fd9d6b Andy McCall 1 month ago ↶
745
746
# [ tokio ::test ( flavor = " multi_thread" ) ]
747
async fn a_wiki_is_edited_on_the_web_and_cloned_and_pushed_as_name_dot_wiki ( ) {
748
hermetic ( ) ;
749
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
750
let mut config = Config:: load( dir.path ( ) ) .unwrap ( ) ;
c8684d44 Andy McCall 20 days ago
751
752
config.registration.setup_code = " test-setup" .into ( ) ;
66fd9d6b Andy McCall 1 month ago ↶
753
config.auth.push_tokens = vec! [ " site" .into ( ) ] ;
754
config.registration.mode = " open" .into ( ) ;
755
let listener = tokio:: net:: TcpListener:: bind( " 127.0.0.1:0" ) .await.unwrap ( ) ;
756
let port = listener.local_addr ( ) .unwrap ( ) .port ( ) ;
757
config.server.base_url = format! ( " http://127.0.0.1:{port} " ) ;
758
let app = oxygenhub:: build( config) .await.unwrap ( ) ;
759
let router = oxygenhub:: router( app.clone ( ) ) ;
760
tokio:: spawn( async move { axum:: serve( listener, router) .await.unwrap ( ) } ) ;
761
let base = format! ( " http://127.0.0.1:{port} " ) ;
762
let app = & app;
c8684d44 Andy McCall 20 days ago
763
let r =
764
send ( app, " POST" , " /register" , None , Some ( " username=andy&email=a%40example.com&password=correct-horse-battery&setup=test-setup" ) )
765
.await;
66fd9d6b Andy McCall 1 month ago ↶
766
let andy = r.cookie.unwrap ( ) ;
767
let andy_id = db:: accounts:: account_by_name( & app.db, " andy" ) .await.unwrap ( ) .unwrap ( ) .id;
768
let repo = RepoName:: parse( " andy/demo" ) .unwrap ( ) ;
769
app.repos.create ( & repo, None ) .unwrap ( ) ;
770
db:: repos:: create( & app.db, " andy" , " demo" , " " , hub_api:: Visibility:: Public) .await.unwrap ( ) ;
771
772
let work = tempfile:: tempdir( ) .unwrap ( ) ;
773
let mut client = Repo:: init( work.path ( ) ) .unwrap ( ) ;
774
client.create_identity ( " Andy McCall" , " [email protected] " , IdentityScope:: Repo) .unwrap ( ) ;
775
client.config.set ( " core.sign" , " change" ) .unwrap ( ) ;
776
client.config.set ( " remote.origin.url" , & format! ( " {base} /andy/demo" ) ) .unwrap ( ) ;
777
client.config.set ( " remote.origin.token" , " site" ) .unwrap ( ) ;
778
client.save_config ( ) .unwrap ( ) ;
779
write ( work.path ( ) , " a.txt" , b " one\n " ) ;
780
client.snapshot ( " t" ) .unwrap ( ) ;
781
client.commit ( " first" , & [ ] ) .unwrap ( ) ;
782
assert! ( client.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
783
client.delegate_add ( app.hub_key.identity.clone ( ) , oxy_core:: identity:: Scope:: CHANGES | oxy_core:: identity:: Scope:: REFS , None ) .unwrap ( ) ;
784
let key = client.config.author_key.unwrap ( ) ;
785
let id = client.identity ( & key) .unwrap ( ) ;
786
db:: accounts:: link_identity( & app.db, andy_id, & key.to_hex ( ) , & id.name, & id.email, & id.encode ( ) ) .await.unwrap ( ) ;
787
788
789
let page = send ( app, " GET" , " /andy/demo/wiki" , Some ( & andy) , None ) .await;
790
assert_eq! ( page.status, StatusCode:: OK , " {}" , page.body) ;
791
assert! ( page.body.contains ( " No wiki yet" ) & & page.body.contains ( " >Wiki</a>" ) & & page.body.contains ( " andy/demo.wiki" ) , " {}" , page.body) ;
792
assert! ( ! send ( app, " GET" , " /andy/demo" , None , None ) .await.body.contains ( " >Wiki</a>" ) , " no tab for a reader without a wiki" ) ;
793
794
let csrf_n = csrf_of ( & send ( app, " GET" , " /new" , Some ( & andy) , None ) .await.body) ;
795
let r = send ( app, " POST" , " /new" , Some ( & andy) , Some ( & format! ( " _csrf={csrf_n} &owner=andy&name=demo.wiki&visibility=public" ) ) ) .await;
796
assert! ( r.body.contains ( " cannot end in .wiki" ) , " {}" , r.body) ;
797
798
799
let csrf = csrf_of ( & page.body) ;
800
let r = send (
801
app,
802
" POST" ,
803
" /andy/demo/wiki/_new/edit" ,
804
Some ( & andy) ,
805
Some ( & format! ( " _csrf={csrf} &title=Home&content={} " , enc ( " # Welcome\n \n See [the guide](Guide).\n " ) ) ) ,
806
)
807
.await;
808
assert_eq! ( r.status, StatusCode:: SEE_OTHER , " {}" , r.body) ;
809
assert_eq! ( r.location.as_deref ( ) , Some ( " /andy/demo/wiki/Home" ) ) ;
810
let home = send ( app, " GET" , " /andy/demo/wiki" , None , None ) .await.body;
811
assert! (
ffb67a81 Andy McCall 23 days ago
812
home.contains ( " Welcome" )
813
& & home.contains ( " href=\" /andy/demo/wiki/Guide\" " )
814
& & home.contains ( " Andy McCall via" )
815
& & home.contains ( " edited this page" )
816
& & home.contains ( " 1 revision" ) ,
66fd9d6b Andy McCall 1 month ago ↶
817
" {home}"
818
) ;
819
assert! ( send ( app, " GET" , " /andy/demo" , None , None ) .await.body.contains ( " >Wiki</a>" ) , " the tab shows to everyone now" ) ;
820
let missing = send ( app, " GET" , " /andy/demo/wiki/Guide" , Some ( & andy) , None ) .await.body;
821
assert! ( missing.contains ( " no page called <strong>Guide</strong>" ) & & missing.contains ( " _new?title=Guide" ) , " {missing}" ) ;
822
823
let r = send (
824
app,
825
" POST" ,
826
" /andy/demo/wiki/Home/edit" ,
827
Some ( & andy) ,
828
Some ( & format! ( " _csrf={csrf} &title=Home&content={} &message=Say+more" , enc ( " # Welcome\n \n More.\n " ) ) ) ,
829
)
830
.await;
831
assert_eq! ( r.location.as_deref ( ) , Some ( " /andy/demo/wiki/Home" ) , " {}" , r.body) ;
832
let hist = send ( app, " GET" , " /andy/demo/wiki/Home/history" , None , None ) .await.body;
833
assert! ( hist.contains ( " Say more" ) & & hist.contains ( " Create Home" ) , " {hist}" ) ;
834
let r = send (
835
app,
836
" POST" ,
837
" /andy/demo/wiki/Home/edit" ,
838
Some ( & andy) ,
839
Some ( & format! ( " _csrf={csrf} &title=Start+Here&content={} " , enc ( " # Start\n " ) ) ) ,
840
)
841
.await;
842
assert_eq! ( r.location.as_deref ( ) , Some ( " /andy/demo/wiki/Start-Here" ) , " {}" , r.body) ;
843
assert! ( send ( app, " GET" , " /andy/demo/wiki/Start-Here" , None , None ) .await.body.contains ( " <h1>Start Here</h1>" ) ) ;
844
assert! ( send ( app, " GET" , " /andy/demo/wiki" , None , None ) .await.body.contains ( " no page called <strong>Home</strong>" ) ) ;
845
let pages = send ( app, " GET" , " /andy/demo/wiki/_pages?q=start" , None , None ) .await.body;
846
assert! ( pages.contains ( " href=\" /andy/demo/wiki/Start-Here\" " ) & & pages.contains ( " mentions" ) , " {pages}" ) ;
847
848
849
let wdir = tempfile:: tempdir( ) .unwrap ( ) ;
850
let mut w = Repo:: clone( & format! ( " {base} /andy/demo.wiki" ) , wdir.path ( ) ) .unwrap ( ) ;
851
assert_eq! ( std:: fs:: read_to_string( wdir.path ( ) .join ( " Start-Here.md" ) ) .unwrap ( ) , " # Start\n " ) ;
852
w.create_identity ( " Andy McCall" , " [email protected] " , IdentityScope:: Repo) .unwrap ( ) ;
853
w.config.set ( " core.sign" , " change" ) .unwrap ( ) ;
854
w.config.set ( " remote.origin.token" , " site" ) .unwrap ( ) ;
855
w.save_config ( ) .unwrap ( ) ;
856
write ( wdir.path ( ) , " Guide.md" , b " # Guide\n \n Pushed from a clone.\n " ) ;
857
w.snapshot ( " t" ) .unwrap ( ) ;
858
w.commit ( " Add the guide" , & [ ] ) .unwrap ( ) ;
859
assert! ( w.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) , " pushed to the wiki" ) ;
860
let guide = send ( app, " GET" , " /andy/demo/wiki/Guide" , None , None ) .await.body;
861
assert! ( guide.contains ( " Pushed from a clone" ) , " {guide}" ) ;
862
assert! (
863
db:: flow:: runs( & app.db, db:: repos:: get( & app.db, " andy" , " demo" ) .await.unwrap ( ) .unwrap ( ) .id, 10 , None )
864
.await
865
.map ( | r | r.is_empty ( ) )
866
.unwrap_or ( true ) ,
867
" a wiki push starts no run"
868
) ;
869
870
871
let r = send ( app, " POST" , " /andy/demo/wiki/Guide/delete" , Some ( & andy) , Some ( & format! ( " _csrf={csrf} " ) ) ) .await;
872
assert_eq! ( r.status, StatusCode:: SEE_OTHER , " {}" , r.body) ;
873
assert! ( send ( app, " GET" , " /andy/demo/wiki/Guide" , None , None ) .await.body.contains ( " no page called" ) ) ;
c7067c7b Andy McCall 17 days ago
874
let settings = send ( app, " GET" , " /andy/demo/settings/advanced" , Some ( & andy) , None ) .await.body;
66fd9d6b Andy McCall 1 month ago ↶
875
assert! ( settings.contains ( " DELETE WIKI DATA" ) , " {settings}" ) ;
876
let r = send (
877
app,
878
" POST" ,
879
" /andy/demo/settings/wiki/delete" ,
880
Some ( & andy) ,
881
Some ( & format! ( " _csrf={} &confirm=andy%2Fdemo" , csrf_of ( & settings) ) ) ,
882
)
883
.await;
c7067c7b Andy McCall 17 days ago
884
let r = settled ( app, r, Some ( & andy) , " /andy/demo/settings/advanced" ) .await;
66fd9d6b Andy McCall 1 month ago ↶
885
assert! ( r.body.contains ( " The wiki is in the trash" ) , " {}" , r.body) ;
886
assert! ( send ( app, " GET" , " /andy/demo/wiki" , Some ( & andy) , None ) .await.body.contains ( " No wiki yet" ) ) ;
887
}
6e7dfa0a Andy McCall 1 month ago ↶
888
889
890
891
892
# [ tokio ::test ( flavor = " multi_thread" ) ]
893
async fn a_wiki_can_start_with_a_push ( ) {
894
hermetic ( ) ;
895
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
896
let mut config = Config:: load( dir.path ( ) ) .unwrap ( ) ;
c8684d44 Andy McCall 20 days ago
897
898
config.registration.setup_code = " test-setup" .into ( ) ;
6e7dfa0a Andy McCall 1 month ago ↶
899
config.auth.push_tokens = vec! [ " site" .into ( ) ] ;
900
let listener = tokio:: net:: TcpListener:: bind( " 127.0.0.1:0" ) .await.unwrap ( ) ;
901
let port = listener.local_addr ( ) .unwrap ( ) .port ( ) ;
902
config.server.base_url = format! ( " http://127.0.0.1:{port} " ) ;
903
let app = oxygenhub:: build( config) .await.unwrap ( ) ;
904
let router = oxygenhub:: router( app.clone ( ) ) ;
905
tokio:: spawn( async move { axum:: serve( listener, router) .await.unwrap ( ) } ) ;
906
let base = format! ( " http://127.0.0.1:{port} " ) ;
907
let repo = RepoName:: parse( " andy/docs" ) .unwrap ( ) ;
908
app.repos.create ( & repo, None ) .unwrap ( ) ;
909
db:: repos:: create( & app.db, " andy" , " docs" , " " , hub_api:: Visibility:: Public) .await.unwrap ( ) ;
910
let url = format! ( " {base} /andy/docs.wiki" ) ;
911
912
913
let anon = tempfile:: tempdir( ) .unwrap ( ) ;
914
let u = url.clone ( ) ;
915
let p = anon.path ( ) .join ( " w" ) ;
916
assert! ( tokio:: task:: spawn_blocking( move | | Repo:: clone( & u, & p) ) .await.unwrap ( ) .is_err ( ) ) ;
917
assert! ( ! app.wikis.exists ( & repo) ) ;
918
919
920
let work = tempfile:: tempdir( ) .unwrap ( ) ;
921
let mut w = Repo:: init( work.path ( ) ) .unwrap ( ) ;
922
w.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
923
w.config.set ( " remote.origin.token" , " site" ) .unwrap ( ) ;
924
w.save_config ( ) .unwrap ( ) ;
925
write ( work.path ( ) , " Home.md" , b " # Docs\n \n See [the overview](Overview).\n " ) ;
926
write ( work.path ( ) , " Overview.md" , b " # Overview\n \n ```mermaid\n flowchart LR\n A --> B\n ```\n " ) ;
927
w.snapshot ( " t" ) .unwrap ( ) ;
928
w.commit ( " First pages" , & [ ] ) .unwrap ( ) ;
929
let pushed = tokio:: task:: spawn_blocking( move | | w.push ( " origin" , false ) .unwrap ( ) ) .await.unwrap ( ) ;
930
assert! ( pushed.iter ( ) .all ( | r | r.ok ) , " {pushed:?}" ) ;
931
assert! ( app.wikis.exists ( & repo) ) ;
932
let home = send ( & app, " GET" , " /andy/docs/wiki" , None , None ) .await.body;
ffb67a81 Andy McCall 23 days ago
933
assert! ( home.contains ( " <h1 id=\" docs\" >Docs</h1>" ) & & home.contains ( " href=\" /andy/docs/wiki/Overview\" " ) , " {home}" ) ;
6e7dfa0a Andy McCall 1 month ago ↶
934
let overview = send ( & app, " GET" , " /andy/docs/wiki/Overview" , None , None ) .await.body;
935
assert! ( overview.contains ( " class=\" language-mermaid\" " ) , " a diagram block for the page's script to draw: {overview}" ) ;
936
}