this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

CAR-based repo validate/reproduce test framework

Idea is to make it easy to add regression tests and weird edge cases for
interop testing by supplying just a DID document (JSON file) and repo
CAR file.

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

This is a binary file and will not be displayed.