upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/docs/archive/2025-11-05-grasp01-test-plan.md
blob: 4148f1d9d1fbcf286dad6c58b00ffe7b7fe1f964 (plain)
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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
# GRASP-01 Test Plan

**Date:** November 5, 2025  
**Status:** Planning Phase  
**Scope:** Complete test coverage for GRASP-01 Core Service Requirements

---

## Overview

This document outlines all tests needed to validate GRASP-01 compliance. Each test maps directly to requirements in `../grasp/01.md`.

**Test Strategy:**
1. Build tests against ngit-relay reference implementation FIRST
2. Each requirement = one or more test functions
3. All tests reference specific spec line numbers
4. Tests organized by spec sections

---

## Test Organization

```
grasp-audit/src/specs/
├── mod.rs                          # Export all test modules
├── nip01_smoke.rs                  # ✅ DONE - Basic relay functionality
├── grasp01_nostr_relay.rs          # NEW - Nostr relay requirements
├── grasp01_git_http.rs             # NEW - Git Smart HTTP requirements
└── grasp01_cors.rs                 # NEW - CORS requirements
```

---

## 1. NIP-01 Smoke Tests (✅ COMPLETE)

**File:** `grasp-audit/src/specs/nip01_smoke.rs`

**Status:** Already implemented and working

**Coverage:**
- ✅ WebSocket connection
- ✅ Send/receive events
- ✅ Subscriptions (REQ/CLOSE)
- ✅ Event validation (signatures, IDs)

**Note:** These are smoke tests only. We don't comprehensively test NIP-01 since rust-nostr already has 1000+ tests.

---

## 2. GRASP-01 Nostr Relay Tests (🔜 TO DO)

**File:** `grasp-audit/src/specs/grasp01_nostr_relay.rs`

**Spec Reference:** Lines 1-14 of `../grasp/01.md`

### Test Functions to Implement:

#### 2.1 Repository Announcement Acceptance

```rust
/// Test: Accept valid repository announcements
/// Spec: Lines 3-5
/// Requirement: MUST accept repo announcements listing service in clone & relays tags
async fn test_accept_valid_repo_announcement()
```

**Test Details:**
- Create kind 30617 event with valid tags
- Include service URL in both `clone` and `relays` tags
- Send to relay
- Verify acceptance (OK response)
- Query back to confirm stored

```rust
/// Test: Reject repo announcements not listing service (unless GRASP-05)
/// Spec: Line 5
/// Requirement: MUST reject announcements not listing service
async fn test_reject_repo_announcement_missing_clone_tag()
```

**Test Details:**
- Create kind 30617 event WITHOUT service in `clone` tag
- Send to relay
- Verify rejection (error response)
- Confirm not stored in relay

```rust
/// Test: Reject repo announcements not listing service in relays tag
/// Spec: Line 5
/// Requirement: MUST reject announcements not listing service in relays
async fn test_reject_repo_announcement_missing_relays_tag()
```

**Test Details:**
- Create kind 30617 event WITHOUT service in `relays` tag
- Send to relay
- Verify rejection
- Confirm not stored

#### 2.2 Repository State Announcement Acceptance

```rust
/// Test: Accept valid repository state announcements
/// Spec: Line 3
/// Requirement: MUST accept repo state announcements
async fn test_accept_valid_repo_state_announcement()
```

**Test Details:**
- First send valid kind 30617 (repo announcement)
- Then send kind 30618 (state announcement) with matching `d` tag
- Include `refs/heads/main` and `HEAD` tags
- Verify acceptance
- Query back to confirm

```rust
/// Test: Accept state announcement with multiple refs
/// Spec: Line 3
/// Requirement: MUST accept state announcements with multiple refs
async fn test_accept_state_announcement_multiple_refs()
```

**Test Details:**
- Send kind 30618 with multiple `refs/heads/*` tags
- Include `refs/tags/*` tags
- Verify all refs are stored

```rust
/// Test: Accept state announcement with no refs (stop tracking)
/// Spec: NIP-34 spec
/// Requirement: Support stopping state tracking
async fn test_accept_state_announcement_no_refs()
```

**Test Details:**
- Send kind 30618 with only `d` tag (no refs)
- Verify acceptance (allows author to stop tracking)

#### 2.3 Related Event Acceptance

```rust
/// Test: Accept events tagging accepted repo announcements
/// Spec: Lines 7-9
/// Requirement: MUST accept events that tag accepted repo announcements
async fn test_accept_event_tagging_repo_announcement()
```

**Test Details:**
- Create and accept kind 30617 (repo announcement)
- Create kind 1621 (issue) with `a` tag pointing to repo
- Verify issue is accepted

```rust
/// Test: Accept events tagged by repo announcements
/// Spec: Lines 7-9
/// Requirement: MUST accept events tagged by accepted announcements
async fn test_accept_event_tagged_by_repo()
```

**Test Details:**
- Create event (e.g., kind 1 note)
- Create kind 30617 that tags the note
- Verify note is accepted/retained

```rust
/// Test: Accept patches (kind 1617) for accepted repos
/// Spec: Lines 8-9
/// Requirement: MUST accept patches for accepted repos
async fn test_accept_patch_for_repo()
```

**Test Details:**
- Create kind 30617 repo announcement
- Create kind 1617 patch with `a` tag to repo
- Verify patch acceptance

```rust
/// Test: Accept pull requests (kind 1618) for accepted repos
/// Spec: Lines 8-9
/// Requirement: MUST accept PRs for accepted repos
async fn test_accept_pull_request_for_repo()
```

**Test Details:**
- Create kind 30617 repo announcement
- Create kind 1618 PR with `a` tag to repo
- Include required tags: `c` (commit), `clone`, etc.
- Verify PR acceptance

```rust
/// Test: Accept issues (kind 1621) for accepted repos
/// Spec: Lines 8-9
/// Requirement: MUST accept issues for accepted repos
async fn test_accept_issue_for_repo()
```

**Test Details:**
- Create kind 30617 repo announcement
- Create kind 1621 issue with `a` tag to repo
- Verify issue acceptance

```rust
/// Test: Accept replies to accepted patches/PRs/issues
/// Spec: Lines 8-9
/// Requirement: MUST accept replies to accepted events
async fn test_accept_reply_to_issue()
```

**Test Details:**
- Create kind 1621 issue
- Create NIP-22 comment (kind 1111) replying to issue
- Verify reply acceptance

#### 2.4 NIP-11 Relay Information

```rust
/// Test: Serve NIP-11 document at /.well-known/nostr.json
/// Spec: Line 11
/// Requirement: MUST serve NIP-11 document
async fn test_nip11_document_exists()
```

**Test Details:**
- HTTP GET to `/.well-known/nostr.json` or `https://domain/` with `Accept: application/nostr+json`
- Verify 200 response
- Verify valid JSON

```rust
/// Test: NIP-11 includes supported_grasps field
/// Spec: Line 12
/// Requirement: MUST list supported GRASPs as string array
async fn test_nip11_supported_grasps_field()
```

**Test Details:**
- Fetch NIP-11 document
- Verify `supported_grasps` field exists
- Verify it's a string array
- Verify includes "GRASP-01"
- Format check: each entry matches `GRASP-XX` pattern

```rust
/// Test: NIP-11 includes repo_acceptance_criteria field
/// Spec: Line 13
/// Requirement: MUST list repository acceptance criteria
async fn test_nip11_repo_acceptance_criteria_field()
```

**Test Details:**
- Fetch NIP-11 document
- Verify `repo_acceptance_criteria` field exists
- Verify it's a human-readable string
- Verify non-empty

```rust
/// Test: NIP-11 curation field handling
/// Spec: Line 14
/// Requirement: MUST include curation if curated, omit otherwise
async fn test_nip11_curation_field()
```

**Test Details:**
- Fetch NIP-11 document
- If `curation` field exists, verify it's a non-empty string
- Document behavior (present or absent is both valid)

#### 2.5 Event Rejection Policies

```rust
/// Test: MAY reject based on custom criteria
/// Spec: Line 6
/// Requirement: Document that custom rejection is allowed
async fn test_custom_rejection_allowed()
```

**Test Details:**
- This is a policy test, not a functional test
- Verify relay can reject for reasons like:
  - Pre-payment required
  - Quota exceeded
  - WoT filtering
  - Whitelist
  - SPAM prevention
- Document in test that this is implementation-specific

```rust
/// Test: MAY reject/delete for SPAM prevention
/// Spec: Line 10
/// Requirement: Generic SPAM prevention allowed
async fn test_spam_prevention_allowed()
```

**Test Details:**
- Document that relay may reject/delete for SPAM
- This is permissive, not mandatory
- Test should document the policy, not enforce specific behavior

---

## 3. GRASP-01 Git Smart HTTP Tests (🔜 TO DO)

**File:** `grasp-audit/src/specs/grasp01_git_http.rs`

**Spec Reference:** Lines 15-31 of `../grasp/01.md`

### Test Functions to Implement:

#### 3.1 Repository Serving

```rust
/// Test: Serve git repo at /<npub>/<identifier>.git
/// Spec: Line 17
/// Requirement: MUST serve git repo at correct path
async fn test_serve_git_repo_at_correct_path()
```

**Test Details:**
- Create kind 30617 announcement with `d` tag = "test-repo"
- Push git data to repository
- HTTP GET to `/<npub>/test-repo.git/info/refs?service=git-upload-pack`
- Verify 200 response
- Verify git smart HTTP response format

```rust
/// Test: Unauthenticated git-upload-pack (clone/fetch)
/// Spec: Line 17
/// Requirement: MUST allow unauthenticated clone/fetch
async fn test_unauthenticated_clone()
```

**Test Details:**
- Create and push repository
- Perform git clone without authentication
- Verify clone succeeds
- Verify repository contents match

```rust
/// Test: Repository only served for accepted announcements
/// Spec: Line 17
/// Requirement: Only serve repos with accepted announcements
async fn test_no_git_repo_without_announcement()
```

**Test Details:**
- Try to access `/<npub>/nonexistent.git/info/refs`
- Verify 404 response
- Verify no git data served

#### 3.2 Push Authorization

```rust
/// Test: Accept push matching latest state announcement
/// Spec: Line 19
/// Requirement: MUST accept pushes matching state announcement
async fn test_accept_push_matching_state()
```

**Test Details:**
- Create kind 30617 repo announcement
- Create kind 30618 state with `refs/heads/main` = commit A
- Attempt git push updating main to commit B (child of A)
- Verify push accepted
- Verify repository updated

```rust
/// Test: Reject push not matching state announcement
/// Spec: Line 19
/// Requirement: Implicit - only accept matching pushes
async fn test_reject_push_not_matching_state()
```

**Test Details:**
- Create kind 30618 state with `refs/heads/main` = commit A
- Attempt git push updating main to commit X (unrelated)
- Verify push rejected
- Verify repository unchanged

```rust
/// Test: Respect recursive maintainer set
/// Spec: Line 19
/// Requirement: MUST respect recursive maintainer set
async fn test_push_authorization_maintainer_set()
```

**Test Details:**
- Create repo announcement by user A
- Add user B to `maintainers` tag
- User B creates state announcement
- User B pushes matching state
- Verify push accepted
- Test recursion: B lists C as maintainer, C can push

```rust
/// Test: Reject push from non-maintainer
/// Spec: Line 19 (implicit)
/// Requirement: Only maintainers can push
async fn test_reject_push_from_non_maintainer()
```

**Test Details:**
- Create repo announcement by user A
- User B (not in maintainers) creates state announcement
- User B attempts push
- Verify push rejected

#### 3.3 HEAD Management

```rust
/// Test: Set HEAD per state announcement
/// Spec: Line 21
/// Requirement: MUST set HEAD when git data received
async fn test_set_head_from_state_announcement()
```

**Test Details:**
- Create kind 30618 with `HEAD = ref: refs/heads/develop`
- Push git data for develop branch
- Clone repository
- Verify HEAD points to develop (not main)

```rust
/// Test: Update HEAD when state changes
/// Spec: Line 21
/// Requirement: Update HEAD as soon as git data available
async fn test_update_head_when_state_changes()
```

**Test Details:**
- Initial state: HEAD = main
- Push new state: HEAD = develop
- Push git data for develop
- Verify HEAD updates to develop

#### 3.4 Pull Request Refs

```rust
/// Test: Accept push to refs/nostr/<event-id>
/// Spec: Line 23
/// Requirement: MUST accept pushes to PR refs
async fn test_accept_push_to_pr_ref()
```

**Test Details:**
- Create kind 1618 PR event
- Push to `refs/nostr/<pr-event-id>`
- Verify push accepted
- Verify ref exists in repository

```rust
/// Test: Reject PR ref if event has different tip
/// Spec: Line 23
/// Requirement: SHOULD reject if tip mismatch
async fn test_reject_pr_ref_tip_mismatch()
```

**Test Details:**
- Create kind 1618 PR with `c` tag = commit A
- Push to `refs/nostr/<pr-event-id>` with commit B
- Verify push rejected (or document if accepted)

```rust
/// Test: Delete PR ref if no event within 20 minutes
/// Spec: Line 23
/// Requirement: SHOULD delete orphaned PR refs
async fn test_delete_orphaned_pr_ref()
```

**Test Details:**
- Push to `refs/nostr/<event-id>`
- Wait 20+ minutes without sending kind 1618/1619 event
- Check if ref is deleted
- Note: This is SHOULD, not MUST - document behavior

```rust
/// Test: Keep PR ref if event exists
/// Spec: Line 23 (implicit)
/// Requirement: Keep ref if valid PR/update event exists
async fn test_keep_pr_ref_with_event()
```

**Test Details:**
- Push to `refs/nostr/<event-id>`
- Send kind 1618 PR event with matching `c` tag
- Wait 20+ minutes
- Verify ref still exists

#### 3.5 Git Protocol Features

```rust
/// Test: Advertise allow-reachable-sha1-in-want
/// Spec: Line 25
/// Requirement: MUST advertise and serve capability
async fn test_advertise_reachable_sha1_in_want()
```

**Test Details:**
- GET `/repo.git/info/refs?service=git-upload-pack`
- Parse git protocol response
- Verify `allow-reachable-sha1-in-want` in capabilities

```rust
/// Test: Advertise allow-tip-sha1-in-want
/// Spec: Line 25
/// Requirement: MUST advertise and serve capability
async fn test_advertise_tip_sha1_in_want()
```

**Test Details:**
- GET `/repo.git/info/refs?service=git-upload-pack`
- Parse git protocol response
- Verify `allow-tip-sha1-in-want` in capabilities

```rust
/// Test: Serve available OIDs by SHA1
/// Spec: Line 25
/// Requirement: MUST serve available OIDs
async fn test_serve_oids_by_sha1()
```

**Test Details:**
- Push repository with known commits
- Perform git fetch with specific SHA1 want
- Verify server provides the object

#### 3.6 Web Interface

```rust
/// Test: Serve webpage at repo endpoint
/// Spec: Line 27
/// Requirement: SHOULD serve webpage with links
async fn test_serve_webpage_at_repo_endpoint()
```

**Test Details:**
- HTTP GET to `/<npub>/<identifier>.git` with `Accept: text/html`
- Verify HTML response (not git protocol)
- Verify links to git nostr clients (optional check)

```rust
/// Test: Serve 404 for non-existent repos
/// Spec: Line 27
/// Requirement: SHOULD serve 404 for missing repos
async fn test_serve_404_for_missing_repo()
```

**Test Details:**
- HTTP GET to `/<npub>/nonexistent.git` with `Accept: text/html`
- Verify 404 response
- Verify helpful error message

---

## 4. GRASP-01 CORS Tests (🔜 TO DO)

**File:** `grasp-audit/src/specs/grasp01_cors.rs`

**Spec Reference:** Lines 32-40 of `../grasp/01.md`

### Test Functions to Implement:

```rust
/// Test: Access-Control-Allow-Origin on all responses
/// Spec: Line 35
/// Requirement: MUST set ACAO: * on ALL responses
async fn test_cors_allow_origin_on_all_responses()
```

**Test Details:**
- Test multiple endpoints:
  - WebSocket upgrade (Nostr relay)
  - Git HTTP endpoints (info/refs, upload-pack, receive-pack)
  - NIP-11 endpoint
  - Web interface
- Verify ALL include `Access-Control-Allow-Origin: *`

```rust
/// Test: Access-Control-Allow-Methods on all responses
/// Spec: Line 36
/// Requirement: MUST set ACAM: GET, POST on ALL responses
async fn test_cors_allow_methods_on_all_responses()
```

**Test Details:**
- Test same endpoints as above
- Verify ALL include `Access-Control-Allow-Methods: GET, POST`

```rust
/// Test: Access-Control-Allow-Headers on all responses
/// Spec: Line 37
/// Requirement: MUST set ACAH: Content-Type on ALL responses
async fn test_cors_allow_headers_on_all_responses()
```

**Test Details:**
- Test same endpoints as above
- Verify ALL include `Access-Control-Allow-Headers: Content-Type`

```rust
/// Test: OPTIONS requests return 204 No Content
/// Spec: Line 38
/// Requirement: MUST respond to OPTIONS with 204
async fn test_cors_options_request()
```

**Test Details:**
- Send OPTIONS request to various endpoints
- Verify 204 No Content response
- Verify CORS headers present on OPTIONS response

```rust
/// Test: CORS headers on error responses
/// Spec: Line 35 (ALL responses)
/// Requirement: CORS headers even on errors
async fn test_cors_headers_on_error_responses()
```

**Test Details:**
- Trigger various error conditions:
  - 404 not found
  - 403 forbidden (unauthorized push)
  - 400 bad request
- Verify CORS headers present on all error responses

```rust
/// Test: Preflight request handling
/// Spec: Lines 35-38
/// Requirement: Full preflight support for web clients
async fn test_cors_preflight_request()
```

**Test Details:**
- Send OPTIONS with Origin and Access-Control-Request-Method headers
- Verify proper preflight response
- Verify subsequent actual request succeeds

---

## Implementation Priority

### Phase 1: Core Nostr Relay Tests (Complete these first)
1. ✅ NIP-01 smoke tests (DONE)
2. Repository announcement acceptance/rejection
3. Repository state announcement acceptance
4. NIP-11 relay information document
5. Related event acceptance (issues, patches, PRs)

### Phase 2: Git Smart HTTP Tests
1. Repository serving at correct paths
2. Unauthenticated clone/fetch
3. Push authorization and maintainer sets
4. HEAD management
5. Git protocol features (SHA1 capabilities)

### Phase 3: Advanced Git Features
1. Pull request refs (refs/nostr/<event-id>)
2. PR ref lifecycle (creation, validation, deletion)
3. Web interface (optional)

### Phase 4: CORS Tests
1. CORS headers on all endpoints
2. OPTIONS request handling
3. Preflight requests
4. Error response CORS

---

## Test Execution Plan

### Against ngit-relay Reference Implementation

```bash
# 1. Start ngit-relay
cd ../ngit-relay
docker-compose up -d

# 2. Run tests
cd ../ngit-grasp/grasp-audit
cargo test --lib  # Unit tests

# Run integration tests by category
cargo test --test grasp01_nostr_relay
cargo test --test grasp01_git_http
cargo test --test grasp01_cors

# 3. Run full audit
cargo run -- --url ws://localhost:8081
```

### Test Data Requirements

For comprehensive testing, we need:
- Multiple test keypairs (maintainers, contributors, non-maintainers)
- Sample git repositories with known commit history
- Valid NIP-34 event templates
- Test data for edge cases

---

## Success Criteria

- [ ] All GRASP-01 requirements have corresponding tests
- [ ] All tests reference specific spec line numbers
- [ ] All tests pass against ngit-relay reference implementation
- [ ] Tests are organized logically by spec sections
- [ ] Clear test output shows what requirement is being tested
- [ ] Tests can be run individually or as full suite
- [ ] Documentation explains what each test validates

---

## Notes

### Spec Line Number References

When implementing tests, use this format:

```rust
/// Test: <Short description>
/// Spec: Lines X-Y of ../grasp/01.md
/// Requirement: <Exact quote or paraphrase from spec>
async fn test_name() {
    // Implementation
}
```

### Test Naming Convention

- `test_accept_*` - Tests that verify acceptance of valid input
- `test_reject_*` - Tests that verify rejection of invalid input
- `test_serve_*` - Tests that verify correct serving of data
- `test_cors_*` - Tests for CORS functionality
- `test_nip11_*` - Tests for NIP-11 relay information

### Edge Cases to Consider

1. **Concurrent updates** - Multiple maintainers pushing simultaneously
2. **Large repositories** - Performance with large git data
3. **Invalid git data** - Corrupted pack files, invalid refs
4. **Event ordering** - State announcement before repo announcement
5. **Deleted events** - What happens when announcement is deleted?
6. **Network failures** - Partial push, interrupted clone
7. **Recursive maintainers** - Deep maintainer chains, circular references

---

**Next Steps:**
1. Implement Phase 1 tests (Nostr relay)
2. Run against ngit-relay to validate
3. Fix any failing tests
4. Move to Phase 2 (Git HTTP)
5. Iterate until all tests pass