github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/registry/regserver/handlers/profile_test.go (about) 1 package handlers 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "net/http/httptest" 10 "strings" 11 "testing" 12 13 crypto "github.com/libp2p/go-libp2p-core/crypto" 14 "github.com/qri-io/qri/auth/key" 15 "github.com/qri-io/qri/registry" 16 ) 17 18 // base64-encoded Test Private Key, decoded in init 19 var ( 20 // peerId: QmZePf5LeXow3RW5U1AgEiNbW46YnRGhZ7HPvm1UmPFPwt 21 testPk1 = `CAASpgkwggSiAgEAAoIBAQC/7Q7fILQ8hc9g07a4HAiDKE4FahzL2eO8OlB1K99Ad4L1zc2dCg+gDVuGwdbOC29IngMA7O3UXijycckOSChgFyW3PafXoBF8Zg9MRBDIBo0lXRhW4TrVytm4Etzp4pQMyTeRYyWR8e2hGXeHArXM1R/A/SjzZUbjJYHhgvEE4OZy7WpcYcW6K3qqBGOU5GDMPuCcJWac2NgXzw6JeNsZuTimfVCJHupqG/dLPMnBOypR22dO7yJIaQ3d0PFLxiDG84X9YupF914RzJlopfdcuipI+6gFAgBw3vi6gbECEzcohjKf/4nqBOEvCDD6SXfl5F/MxoHurbGBYB2CJp+FAgMBAAECggEAaVOxe6Y5A5XzrxHBDtzjlwcBels3nm/fWScvjH4dMQXlavwcwPgKhy2NczDhr4X69oEw6Msd4hQiqJrlWd8juUg6vIsrl1wS/JAOCS65fuyJfV3Pw64rWbTPMwO3FOvxj+rFghZFQgjg/i45uHA2UUkM+h504M5Nzs6Arr/rgV7uPGR5e5OBw3lfiS9ZaA7QZiOq7sMy1L0qD49YO1ojqWu3b7UaMaBQx1Dty7b5IVOSYG+Y3U/dLjhTj4Hg1VtCHWRm3nMOE9cVpMJRhRzKhkq6gnZmni8obz2BBDF02X34oQLcHC/Wn8F3E8RiBjZDI66g+iZeCCUXvYz0vxWAQQKBgQDEJu6flyHPvyBPAC4EOxZAw0zh6SF/r8VgjbKO3n/8d+kZJeVmYnbsLodIEEyXQnr35o2CLqhCvR2kstsRSfRz79nMIt6aPWuwYkXNHQGE8rnCxxyJmxV4S63GczLk7SIn4KmqPlCI08AU0TXJS3zwh7O6e6kBljjPt1mnMgvr3QKBgQD6fAkdI0FRZSXwzygx4uSg47Co6X6ESZ9FDf6ph63lvSK5/eue/ugX6p/olMYq5CHXbLpgM4EJYdRfrH6pwqtBwUJhlh1xI6C48nonnw+oh8YPlFCDLxNG4tq6JVo071qH6CFXCIank3ThZeW5a3ZSe5pBZ8h4bUZ9H8pJL4C7yQKBgFb8SN/+/qCJSoOeOcnohhLMSSD56MAeK7KIxAF1jF5isr1TP+rqiYBtldKQX9bIRY3/8QslM7r88NNj+aAuIrjzSausXvkZedMrkXbHgS/7EAPflrkzTA8fyH10AsLgoj/68mKr5bz34nuY13hgAJUOKNbvFeC9RI5g6eIqYH0FAoGAVqFTXZp12rrK1nAvDKHWRLa6wJCQyxvTU8S1UNi2EgDJ492oAgNTLgJdb8kUiH0CH0lhZCgr9py5IKW94OSM6l72oF2UrS6PRafHC7D9b2IV5Al9lwFO/3MyBrMocapeeyaTcVBnkclz4Qim3OwHrhtFjF1ifhP9DwVRpuIg+dECgYANwlHxLe//tr6BM31PUUrOxP5Y/cj+ydxqM/z6papZFkK6Mvi/vMQQNQkh95GH9zqyC5Z/yLxur4ry1eNYty/9FnuZRAkEmlUSZ/DobhU0Pmj8Hep6JsTuMutref6vCk2n02jc9qYmJuD7iXkdXDSawbEG6f5C4MUkJ38z1t1OjA==` 22 privKey1 crypto.PrivKey 23 24 testPk2 = `CAASqAkwggSkAgEAAoIBAQDdqbl7nT6hQnTDD+nMkrSLzyoqnx2l+kfF2GN7hZDQGMbh5VgvXyEUifnczUbEIGT/llyOdQmDIvsiGBCMU1T+P1MuhzxSKgblrLtp7yAf6jUgQU6GsbJ5r+MvstG6ds7QqPgKidJL302V0+FMJP6nmpupowDxYQe5GqGJuGwNYBqGTrxqM4FsWNquNPmuE0vCDLqYs2vm6ur2k5RIyTXhnFbpHyO31qsgU5d1dR/Wda0KlyQrS0k3Cmj1foRFGuKJKDJVJ1FTryLAWv9VDSCooQKpWUQ3cUuUSuw9OmTnuvC2xx0IaDAjlh8l+4FRbA+nySVsk82B30MlGYc6jSyDAgMBAAECggEAEhvNhWXBOhddxpnENew+R7Wy8ixxlZ+uwWD+L5cnz3hWtxmvbJ9O6oijGwDCKT+kQKUeBp1VG5t9/LkOkQg1x1eRChoOOYApdBX6cZsResn9cRckvShDNmHCI6FuNNeD6dQD/4hm37/sbLMUks3q5/JfiSpB53ZP1TVxwPiKC0WJriS+dHC6kuiilA0uA+lgOD/w2voqeiFQrjcDu71b3DUulamwq3zt4h2I5pnaOKw7N22k2T9rADS7WbBHVIdd7bxgLkc6EEyho7PT4HqOH15QVS9B4Y4xIYVk0Osqq+uDqSTNEn8SBtL9RE3sO7ygQoKgLL3uvRSXGwP4ISNcYQKBgQD5sBrICL+7+JyAxieirR8kHUS2pfn6/rMrx8lu/fHs6yjOpeHojlKdaCcWLkYn5iahCwayNMFiu/0S9mA3FhNI+nANHbGH/I8RET3EJVPPvdfPhP9YpAXiQYLO5OaSdoKrEyqURDUI3ve+xy/2+JSX4R9q8ovH8c2m7L6gjzlMGQKBgQDjRECMkOxoOxC4i/j3tGI30qz+vYFtpDwz34cEKyKo/tEdmG6PWvm1NlY/GJNvi8fVBxPsIoP1m6ys/842ALYygwm4pV3c3xO8XdrJ8k8jfy6UzbsHXXsoTx9ofqz34IYJ71Pw8MGdwgkwr1qTizKB28E4c22CaAg6yudTcU4Q+wKBgQChh/preq1/z8B/1rIBnfpNhNnVR99HL8t+AUwhkAwY97F4rvxNVPXBe4X95YXhfhVzjgyQ8WxCkdeRku5/9LoZNluTQKh/jzaHFh5dbMCh3vFlAWeoUsSzsSoM6yz3h8/VGRssvEuLJ6QjOf2fywVmlG+c4rjna1leKj7Q5Jdu0QKBgF+5W8bZM/ojBsP0kQUkgUop/pu9jkp0JrdiqyfiU1MDIWlpzweqtgrRvDoPS+pr4dukg4uubg6BZ5XmmSC95AAamXmgjYx+mX15urHc0eCNrT0X+nL7uOgdi4kj8g7mDw8YMy8E+UhNdjl/YpNKyhdQTG5OkA2ha/X3iL/otY0JAoGBAM2E6orKtOiJuLHA+kZfp5BdSNpx5QYGtG+hnPuHspxmFHR8Kj5LgQgJwUsZ6aXtcxPpYOEERAjehJ66CIREHfsy2l1BPdtMPlHIPnnYWSrwtQg0T38VSzhIrFBenOcwg27EGiwPZGccw3JrgyWKRJ7zB5DILlC9306Hz4JNwtGH` 25 privKey2 crypto.PrivKey 26 ) 27 28 func init() { 29 var err error 30 privKey1, err = key.DecodeB64PrivKey(testPk1) 31 if err != nil { 32 panic(err) 33 } 34 privKey2, err = key.DecodeB64PrivKey(testPk2) 35 if err != nil { 36 panic(err) 37 } 38 } 39 40 func TestProfile(t *testing.T) { 41 s := httptest.NewServer(NewRoutes(registry.Registry{Profiles: registry.NewMemProfiles()})) 42 43 p1, err := registry.ProfileFromPrivateKey(®istry.Profile{Username: "b5"}, privKey1) 44 if err != nil { 45 t.Errorf("error generating profile: %s", err.Error()) 46 return 47 } 48 49 p2, err := registry.ProfileFromPrivateKey(®istry.Profile{Username: "b5"}, privKey2) 50 if err != nil { 51 t.Errorf("error generating profile: %s", err.Error()) 52 return 53 } 54 55 p1Rename, err := registry.ProfileFromPrivateKey(®istry.Profile{Username: "b6"}, privKey1) 56 if err != nil { 57 t.Errorf("error generating profile: %s", err.Error()) 58 return 59 } 60 61 type env struct { 62 Data *registry.Profile 63 Meta struct { 64 Code int 65 } 66 } 67 68 b5 := ®istry.Profile{ 69 ProfileID: "QmZePf5LeXow3RW5U1AgEiNbW46YnRGhZ7HPvm1UmPFPwt", 70 Username: "b5", 71 PublicKey: "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/7Q7fILQ8hc9g07a4HAiDKE4FahzL2eO8OlB1K99Ad4L1zc2dCg+gDVuGwdbOC29IngMA7O3UXijycckOSChgFyW3PafXoBF8Zg9MRBDIBo0lXRhW4TrVytm4Etzp4pQMyTeRYyWR8e2hGXeHArXM1R/A/SjzZUbjJYHhgvEE4OZy7WpcYcW6K3qqBGOU5GDMPuCcJWac2NgXzw6JeNsZuTimfVCJHupqG/dLPMnBOypR22dO7yJIaQ3d0PFLxiDG84X9YupF914RzJlopfdcuipI+6gFAgBw3vi6gbECEzcohjKf/4nqBOEvCDD6SXfl5F/MxoHurbGBYB2CJp+FAgMBAAE=", 72 } 73 74 b6 := ®istry.Profile{ 75 ProfileID: "QmZePf5LeXow3RW5U1AgEiNbW46YnRGhZ7HPvm1UmPFPwt", 76 Username: "b6", 77 PublicKey: "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/7Q7fILQ8hc9g07a4HAiDKE4FahzL2eO8OlB1K99Ad4L1zc2dCg+gDVuGwdbOC29IngMA7O3UXijycckOSChgFyW3PafXoBF8Zg9MRBDIBo0lXRhW4TrVytm4Etzp4pQMyTeRYyWR8e2hGXeHArXM1R/A/SjzZUbjJYHhgvEE4OZy7WpcYcW6K3qqBGOU5GDMPuCcJWac2NgXzw6JeNsZuTimfVCJHupqG/dLPMnBOypR22dO7yJIaQ3d0PFLxiDG84X9YupF914RzJlopfdcuipI+6gFAgBw3vi6gbECEzcohjKf/4nqBOEvCDD6SXfl5F/MxoHurbGBYB2CJp+FAgMBAAE=", 78 } 79 80 cases := []struct { 81 method string 82 contentType string 83 profile *registry.Profile 84 resStatus int 85 res *env 86 }{ 87 {"OPTIONS", "", nil, http.StatusBadRequest, nil}, 88 {"OPTIONS", "application/json", nil, http.StatusBadRequest, nil}, 89 {"OPTIONS", "application/json", ®istry.Profile{Username: "foo"}, http.StatusNotFound, nil}, 90 {"POST", "", nil, http.StatusBadRequest, nil}, 91 {"POST", "application/json", nil, http.StatusBadRequest, nil}, 92 {"POST", "application/json", ®istry.Profile{Username: p1.Username}, http.StatusBadRequest, nil}, 93 {"POST", "application/json", ®istry.Profile{Username: p1.Username, ProfileID: p1.ProfileID}, http.StatusBadRequest, nil}, 94 {"POST", "application/json", ®istry.Profile{Username: p1.Username, ProfileID: p1.ProfileID, Signature: p1.Signature}, http.StatusBadRequest, nil}, 95 {"POST", "application/json", p1, http.StatusOK, nil}, 96 {"GET", "application/json", ®istry.Profile{Username: b5.Username}, http.StatusOK, &env{Data: b5}}, 97 {"GET", "application/json", ®istry.Profile{Username: "b5"}, http.StatusOK, nil}, 98 {"GET", "application/json", ®istry.Profile{Username: "b6"}, http.StatusNotFound, nil}, 99 {"GET", "application/json", ®istry.Profile{ProfileID: b5.ProfileID}, http.StatusOK, nil}, 100 {"GET", "application/json", ®istry.Profile{ProfileID: "fooooo"}, http.StatusNotFound, nil}, 101 {"POST", "application/json", p1, http.StatusOK, nil}, 102 {"POST", "application/json", p2, http.StatusBadRequest, nil}, 103 {"POST", "application/json", p1Rename, http.StatusOK, nil}, 104 {"GET", "application/json", ®istry.Profile{Username: b6.Username}, http.StatusOK, &env{Data: b6}}, 105 {"DELETE", "", p1Rename, http.StatusBadRequest, nil}, 106 {"DELETE", "application/json", nil, http.StatusBadRequest, nil}, 107 {"DELETE", "application/json", ®istry.Profile{Username: p1.Username, ProfileID: p1.ProfileID, Signature: p1.Signature}, http.StatusBadRequest, nil}, 108 {"DELETE", "application/json", p1Rename, http.StatusOK, nil}, 109 } 110 111 for i, c := range cases { 112 req, err := http.NewRequest(c.method, fmt.Sprintf("%s/registry/profile", s.URL), nil) 113 if err != nil { 114 t.Errorf("case %d error creating request: %s", i, err.Error()) 115 continue 116 } 117 118 if c.contentType != "" { 119 req.Header.Set("Content-Type", c.contentType) 120 } 121 if c.profile != nil { 122 data, err := json.Marshal(c.profile) 123 if err != nil { 124 t.Errorf("error marshaling json body: %s", err.Error()) 125 return 126 } 127 req.Body = ioutil.NopCloser(bytes.NewReader([]byte(data))) 128 } 129 130 res, err := http.DefaultClient.Do(req) 131 if res.StatusCode != c.resStatus { 132 t.Errorf("case %d res status mismatch. expected: %d, got: %d", i, c.resStatus, res.StatusCode) 133 continue 134 } 135 136 if c.res != nil { 137 e := &env{} 138 if err := json.NewDecoder(res.Body).Decode(e); err != nil { 139 t.Errorf("case %d error reading response body: %s", i, err.Error()) 140 continue 141 } 142 143 // if len(e.Data) != len(c.res.Data) { 144 // t.Errorf("case %d reponse body mismatch. expected %d, got: %d", i, len(e.Data), len(c.res.Data)) 145 // continue 146 // } 147 if e.Data.Username != c.res.Data.Username { 148 t.Errorf("case %d reponse username mismatch. expected %s, got: %s", i, e.Data.Username, c.res.Data.Username) 149 } 150 151 // TODO - check each response for profile matches 152 } 153 } 154 } 155 156 func TestProfiles(t *testing.T) { 157 s := httptest.NewServer(NewRoutes(registry.Registry{Profiles: registry.NewMemProfiles()})) 158 159 p1, err := registry.ProfileFromPrivateKey(®istry.Profile{Username: "b5"}, privKey1) 160 if err != nil { 161 t.Errorf("error generating profile: %s", err.Error()) 162 return 163 } 164 165 p1Rename, err := registry.ProfileFromPrivateKey(®istry.Profile{Username: "b6"}, privKey1) 166 if err != nil { 167 t.Errorf("error generating profile: %s", err.Error()) 168 return 169 } 170 171 type env struct { 172 Data []*registry.Profile 173 Meta struct { 174 Code int 175 } 176 } 177 178 b5 := ®istry.Profile{ 179 ProfileID: "QmZePf5LeXow3RW5U1AgEiNbW46YnRGhZ7HPvm1UmPFPwt", 180 Username: "b5", 181 PublicKey: "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/7Q7fILQ8hc9g07a4HAiDKE4FahzL2eO8OlB1K99Ad4L1zc2dCg+gDVuGwdbOC29IngMA7O3UXijycckOSChgFyW3PafXoBF8Zg9MRBDIBo0lXRhW4TrVytm4Etzp4pQMyTeRYyWR8e2hGXeHArXM1R/A/SjzZUbjJYHhgvEE4OZy7WpcYcW6K3qqBGOU5GDMPuCcJWac2NgXzw6JeNsZuTimfVCJHupqG/dLPMnBOypR22dO7yJIaQ3d0PFLxiDG84X9YupF914RzJlopfdcuipI+6gFAgBw3vi6gbECEzcohjKf/4nqBOEvCDD6SXfl5F/MxoHurbGBYB2CJp+FAgMBAAE=", 182 } 183 184 b6 := ®istry.Profile{ 185 ProfileID: "QmZePf5LeXow3RW5U1AgEiNbW46YnRGhZ7HPvm1UmPFPwt", 186 Username: "b6", 187 PublicKey: "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/7Q7fILQ8hc9g07a4HAiDKE4FahzL2eO8OlB1K99Ad4L1zc2dCg+gDVuGwdbOC29IngMA7O3UXijycckOSChgFyW3PafXoBF8Zg9MRBDIBo0lXRhW4TrVytm4Etzp4pQMyTeRYyWR8e2hGXeHArXM1R/A/SjzZUbjJYHhgvEE4OZy7WpcYcW6K3qqBGOU5GDMPuCcJWac2NgXzw6JeNsZuTimfVCJHupqG/dLPMnBOypR22dO7yJIaQ3d0PFLxiDG84X9YupF914RzJlopfdcuipI+6gFAgBw3vi6gbECEzcohjKf/4nqBOEvCDD6SXfl5F/MxoHurbGBYB2CJp+FAgMBAAE=", 188 } 189 190 cases := []struct { 191 method string 192 endpoint string 193 contentType string 194 profile *registry.Profile 195 resStatus int 196 res *env 197 }{ 198 {"GET", "/registry/profiles", "", nil, http.StatusOK, &env{}}, 199 {"POST", "/registry/profile", "application/json", p1, http.StatusOK, nil}, 200 {"GET", "/registry/profiles", "", nil, http.StatusOK, &env{Data: []*registry.Profile{b5}}}, 201 {"POST", "/registry/profile", "application/json", p1Rename, http.StatusOK, nil}, 202 {"GET", "/registry/profiles", "", nil, http.StatusOK, &env{Data: []*registry.Profile{b6}}}, 203 {"DELETE", "/registry/profile", "application/json", p1Rename, http.StatusOK, nil}, 204 {"GET", "/registry/profiles", "", nil, http.StatusOK, &env{Data: []*registry.Profile{}}}, 205 } 206 207 for i, c := range cases { 208 req, err := http.NewRequest(c.method, fmt.Sprintf("%s%s", s.URL, c.endpoint), nil) 209 if err != nil { 210 t.Errorf("case %d error creating request: %s", i, err.Error()) 211 continue 212 } 213 214 if c.contentType != "" { 215 req.Header.Set("Content-Type", c.contentType) 216 } 217 if c.profile != nil { 218 data, err := json.Marshal(c.profile) 219 if err != nil { 220 t.Errorf("error marshaling json body: %s", err.Error()) 221 return 222 } 223 req.Body = ioutil.NopCloser(bytes.NewReader([]byte(data))) 224 } 225 226 res, err := http.DefaultClient.Do(req) 227 if res.StatusCode != c.resStatus { 228 t.Errorf("case %d res status mismatch. expected: %d, got: %d", i, c.resStatus, res.StatusCode) 229 continue 230 } 231 232 if c.res != nil { 233 e := &env{} 234 if err := json.NewDecoder(res.Body).Decode(e); err != nil { 235 t.Errorf("case %d error reading response body: %s", i, err.Error()) 236 continue 237 } 238 239 if len(e.Data) != len(c.res.Data) { 240 t.Errorf("case %d reponse body mismatch. expected %d, got: %d", i, len(e.Data), len(c.res.Data)) 241 continue 242 } 243 244 // TODO - check each response for profile matches 245 } 246 } 247 } 248 249 func TestPostProfiles(t *testing.T) { 250 un := "username" 251 pw := "password" 252 s := httptest.NewServer(NewRoutes(registry.Registry{Profiles: registry.NewMemProfiles()}, AddProtector(NewBAProtector(un, pw)))) 253 const profiles = `[ 254 { 255 "ProfileID": "QmamJUR83rGtDMEvugcC2gtLDx2nhZUTzpzhH6MA2Pb3Md", 256 "Handle": "EDGI", 257 "PublicKey": "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCmTFRx/6dKmoxje8AG+jFv94IcGUGnjrupa7XEr12J/c4ZLn3aPrD8F0tjRbstt1y/J+bO7Qb69DGiu2iSIqyE21nl2oex5+14jtxbupRq9jRTbpUHRj+y9I7uUDwl0E2FS1IQpBBfEGzDPIBVavxbhguC3O3XA7Aq7vea2lpJ1tWpr0GDRYSNmJAybkHS6k7dz1eVXFK+JE8FGFJi/AThQZKWRijvWFdlZvb8RyNFRHzpbr9fh38bRMTqhZpw/YGO5Ly8PNSiOOE4Y5cNUHLEYwG2/lpT4l53iKScsaOazlRkJ6NmkM1il7riCa55fcIAQZDtaAx+CT5ZKfmek4P5AgMBAAE=", 258 "Created": "2018-05-01T22:31:18.288004308Z" 259 }, 260 { 261 "ProfileID": "QmSyDX5LYTiwQi861F5NAwdHrrnd1iRGsoEvCyzQMUyZ4W", 262 "Handle": "b5", 263 "PublicKey": "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/W17VPFX+pjyM1MHI1CsvIe+JYQX45MJNITTd7hZZDX2rRWVavGXhsccmVDGU6ubeN3t6ewcBlgCxvyewwKhmZKCAs3/0xNGKXK/YMyZpRVjTWw9yPU9gOzjd9GuNJtL7d1Hl7dPt9oECa7WBCh0W9u2IoHTda4g8B2mK92awLOZTjXeA7vbhKKX+QVHKDxEI0U2/ooLYJUVxEoHRc+DUYNPahX5qRgJ1ZDP4ep1RRRoZR+HjGhwgJP+IwnAnO5cRCWUbZvE1UBJUZDvYMqW3QvDp+TtXwqUWVvt69tp8EnlBgfyXU91A58IEQtLgZ7klOzdSEJDP+S8HIwhG/vbTAgMBAAE=", 264 "Created": "2018-04-19T22:10:49.909268968Z" 265 } 266 ]` 267 268 req, err := http.NewRequest("POST", fmt.Sprintf("%s/registry/profiles", s.URL), strings.NewReader(profiles)) 269 if err != nil { 270 t.Error(err.Error()) 271 return 272 } 273 274 req.Header.Set("Content-Type", "application/json") 275 req.SetBasicAuth(un, pw) 276 277 res, err := http.DefaultClient.Do(req) 278 if err != nil { 279 t.Error(err.Error()) 280 } 281 282 if res.StatusCode != 200 { 283 t.Errorf("response status mismatch. expected 200, got: %d", res.StatusCode) 284 } 285 286 }