porting all github actions from bluesky-social/indigo to tangled CI
at main 6.7 kB view raw
1package testing 2 3import ( 4 "bytes" 5 "context" 6 "encoding/hex" 7 "encoding/json" 8 "fmt" 9 "os" 10 "strings" 11 "testing" 12 13 appbsky "github.com/bluesky-social/indigo/api/bsky" 14 "github.com/bluesky-social/indigo/repo" 15 "github.com/bluesky-social/indigo/util" 16 17 "github.com/ipfs/go-cid" 18 "github.com/ipfs/go-datastore" 19 blockstore "github.com/ipfs/go-ipfs-blockstore" 20 "github.com/stretchr/testify/assert" 21 cbg "github.com/whyrusleeping/cbor-gen" 22 "github.com/whyrusleeping/go-did" 23) 24 25// deep verificatoin of repo: signature (against DID doc), MST structure, 26// record encoding (JSON and CBOR), etc 27func deepReproduceRepo(t *testing.T, carPath, docPath string) { 28 ctx := context.TODO() 29 assert := assert.New(t) 30 31 // NOTE bgs/bgs.go:537 if we need to parse handle from did doc 32 didDoc := mustReadDidDoc(t, docPath) 33 pubkey, err := didDoc.GetPublicKey("#atproto") 34 if err != nil { 35 t.Fatal(err) 36 } 37 38 fi, err := os.Open(carPath) 39 if err != nil { 40 t.Fatal(err) 41 } 42 43 origRepo, err := repo.ReadRepoFromCar(ctx, fi) 44 if err != nil { 45 t.Fatal(err) 46 } 47 48 // verify signature against pubkey 49 scommit := origRepo.SignedCommit() 50 msg, err := scommit.Unsigned().BytesForSigning() 51 if err != nil { 52 t.Fatal(err) 53 } 54 if err := pubkey.Verify(msg, scommit.Sig); err != nil { 55 fmt.Printf("didDoc: %v\n", didDoc) 56 fmt.Printf("key: %v\n", pubkey) 57 fmt.Printf("sig: %v\n", scommit.Sig) 58 assert.NoError(err) 59 } 60 61 // enumerate all keys 62 repoMap := make(map[string]cid.Cid) 63 err = origRepo.ForEach(ctx, "", func(k string, v cid.Cid) error { 64 repoMap[k] = v 65 return nil 66 }) 67 if err != nil { 68 t.Fatal(err) 69 } 70 71 bs := blockstore.NewBlockstore(datastore.NewMapDatastore()) 72 secondRepo := repo.NewRepo(ctx, didDoc.ID.String(), bs) 73 for p, c := range repoMap { 74 _, rec, err := origRepo.GetRecord(ctx, p) 75 if err != nil { 76 t.Fatal(err) 77 } 78 reproduceRecord(t, p, c, rec) 79 secondRepo.PutRecord(ctx, p, rec) 80 } 81 82 // verify MST tree reproduced 83 kmgr := &util.FakeKeyManager{} 84 _, _, err = secondRepo.Commit(ctx, kmgr.SignForUser) 85 if err != nil { 86 t.Fatal(err) 87 } 88 secondCommit := secondRepo.SignedCommit() 89 assert.Equal(scommit.Data.String(), secondCommit.Data.String()) 90} 91 92// from JSON file on disk 93func mustReadDidDoc(t *testing.T, docPath string) did.Document { 94 var didDoc did.Document 95 docFile, err := os.Open(docPath) 96 if err != nil { 97 t.Fatal(err) 98 } 99 if err := json.NewDecoder(docFile).Decode(&didDoc); err != nil { 100 t.Fatal(err) 101 } 102 return didDoc 103} 104 105// deserializes and re-serializes in a couple different ways and verifies CID 106func reproduceRecord(t *testing.T, path string, c cid.Cid, rec cbg.CBORMarshaler) { 107 assert := assert.New(t) 108 // 0x71 = dag-cbor, 0x12 = sha2-256, 0 = default length 109 cidBuilder := cid.V1Builder{Codec: 0x71, MhType: 0x12, MhLength: 0} 110 recordCBOR := new(bytes.Buffer) 111 nsid := strings.SplitN(path, "/", 2)[0] 112 113 // TODO: refactor this to be short+generic 114 switch nsid { 115 case "app.bsky.feed.post": 116 var recordRepro appbsky.FeedPost 117 recordOrig, suc := rec.(*appbsky.FeedPost) 118 assert.Equal(true, suc) 119 recordJSON, err := json.Marshal(recordOrig) 120 assert.NoError(err) 121 assert.NoError(json.Unmarshal(recordJSON, &recordRepro)) 122 assert.Equal(*recordOrig, recordRepro) 123 assert.NoError(recordRepro.MarshalCBOR(recordCBOR)) 124 reproCID, err := cidBuilder.Sum(recordCBOR.Bytes()) 125 assert.NoError(err) 126 if c.String() != reproCID.String() { 127 fmt.Println(string(recordJSON)) 128 fmt.Println(hex.EncodeToString(recordCBOR.Bytes())) 129 recordBytes := new(bytes.Buffer) 130 assert.NoError(rec.MarshalCBOR(recordBytes)) 131 fmt.Println(hex.EncodeToString(recordBytes.Bytes())) 132 } 133 assert.Equal(c.String(), reproCID.String()) 134 case "app.bsky.actor.profile": 135 var recordRepro appbsky.ActorProfile 136 recordOrig, suc := rec.(*appbsky.ActorProfile) 137 138 assert.Equal(true, suc) 139 recordJSON, err := json.Marshal(recordOrig) 140 assert.NoError(err) 141 assert.NoError(json.Unmarshal(recordJSON, &recordRepro)) 142 assert.Equal(*recordOrig, recordRepro) 143 assert.NoError(recordRepro.MarshalCBOR(recordCBOR)) 144 reproCID, err := cidBuilder.Sum(recordCBOR.Bytes()) 145 assert.NoError(err) 146 assert.Equal(c.String(), reproCID.String()) 147 case "app.bsky.graph.follow": 148 var recordRepro appbsky.GraphFollow 149 recordOrig, suc := rec.(*appbsky.GraphFollow) 150 151 assert.Equal(true, suc) 152 recordJSON, err := json.Marshal(recordOrig) 153 assert.NoError(err) 154 assert.NoError(json.Unmarshal(recordJSON, &recordRepro)) 155 assert.Equal(*recordOrig, recordRepro) 156 assert.NoError(recordRepro.MarshalCBOR(recordCBOR)) 157 reproCID, err := cidBuilder.Sum(recordCBOR.Bytes()) 158 assert.NoError(err) 159 assert.Equal(c.String(), reproCID.String()) 160 case "app.bsky.feed.repost": 161 var recordRepro appbsky.FeedRepost 162 recordOrig, suc := rec.(*appbsky.FeedRepost) 163 164 assert.Equal(true, suc) 165 recordJSON, err := json.Marshal(recordOrig) 166 assert.NoError(err) 167 assert.NoError(json.Unmarshal(recordJSON, &recordRepro)) 168 assert.Equal(*recordOrig, recordRepro) 169 assert.NoError(recordRepro.MarshalCBOR(recordCBOR)) 170 reproCID, err := cidBuilder.Sum(recordCBOR.Bytes()) 171 assert.NoError(err) 172 assert.Equal(c.String(), reproCID.String()) 173 case "app.bsky.feed.like": 174 var recordRepro appbsky.FeedLike 175 recordOrig, suc := rec.(*appbsky.FeedLike) 176 177 assert.Equal(true, suc) 178 recordJSON, err := json.Marshal(recordOrig) 179 assert.NoError(err) 180 assert.NoError(json.Unmarshal(recordJSON, &recordRepro)) 181 assert.Equal(*recordOrig, recordRepro) 182 assert.NoError(recordRepro.MarshalCBOR(recordCBOR)) 183 reproCID, err := cidBuilder.Sum(recordCBOR.Bytes()) 184 assert.NoError(err) 185 assert.Equal(c.String(), reproCID.String()) 186 default: 187 t.Fatal(fmt.Errorf("unsupported record type: %s", nsid)) 188 } 189} 190 191func TestReproduceRepo(t *testing.T) { 192 193 // to get from prod, first resolve handle then save DID doc and repo CAR file like: 194 // http get https://bsky.social/xrpc/com.atproto.identity.resolveHandle handle==greenground.bsky.social 195 // http get https://plc.directory/did:plc:wqgdnqlv2mwiio6pfchwtrff > greenground.didDoc.json 196 // http get https://bsky.social/xrpc/com.atproto.sync.getRepo did==did:plc:wqgdnqlv2mwiio6pfchwtrff > greenground.repo.car 197 198 // to fetch from local dev: 199 // http get localhost:2582/did:plc:dpg45vsnuir2vqqqadsn6afg > fakermaker.didDoc.json 200 // http get localhost:2583/xrpc/com.atproto.sync.getRepo did==did:plc:dpg45vsnuir2vqqqadsn6afg > fakermaker.repo.car 201 202 deepReproduceRepo(t, "testdata/greenground.repo.car", "testdata/greenground.didDoc.json") 203 204 // TODO: update this with the now working p256 code 205 //deepReproduceRepo(t, "testdata/fakermaker.repo.car", "testdata/fakermaker.didDoc.json") 206 207 // XXX: currently failing 208 //deepReproduceRepo(t, "testdata/paul_staging.repo.car", "testdata/paul_staging.didDoc.json") 209}