github.com/decred/politeia@v1.4.0/politeiad/backendv2/tstorebe/plugins/pi/cmds_test.go (about) 1 // Copyright (c) 2021 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package pi 6 7 import ( 8 "encoding/hex" 9 "encoding/json" 10 "errors" 11 "strconv" 12 "testing" 13 14 "github.com/decred/politeia/politeiad/api/v1/identity" 15 backend "github.com/decred/politeia/politeiad/backendv2" 16 "github.com/decred/politeia/politeiad/plugins/pi" 17 ) 18 19 func TestCmdBillingStatus(t *testing.T) { 20 // Setup pi plugin 21 p, cleanup := newTestPiPlugin(t) 22 defer cleanup() 23 24 // Setup an identity that will be used to create the payload 25 // signatures. 26 fid, err := identity.New() 27 if err != nil { 28 t.Fatal(err) 29 } 30 31 // Setup test data 32 var ( 33 // Valid input 34 token = "45154fb45664714b" 35 status = pi.BillingStatusCompleted 36 publicKey = fid.Public.String() 37 38 msg = token + strconv.Itoa(int(status)) 39 signatureb = fid.SignMessage([]byte(msg)) 40 signature = hex.EncodeToString(signatureb[:]) 41 42 msgWithClosed = token + strconv.Itoa(int(pi.BillingStatusClosed)) 43 signaturebWithClosed = fid.SignMessage([]byte(msgWithClosed)) 44 signatureWithClosed = hex.EncodeToString(signaturebWithClosed[:]) 45 46 // signatureIsWrong is a valid hex encoded, ed25519 signature, 47 // but that does not correspond to the valid input parameters 48 // listed above. 49 signatureIsWrong = "b387f678e1236ca1784c4bc77912c754c6b122dd8b" + 50 "3e499617706dd0bd09167a113e59339d2ce4b3570af37a092ba88f39e7f" + 51 "c93a5ac7513e52dca3e5e13f705" 52 ) 53 tokenb, err := hex.DecodeString(token) 54 if err != nil { 55 t.Fatal(err) 56 } 57 58 // Setup tests 59 var tests = []struct { 60 name string // Test name 61 token []byte 62 sbs pi.SetBillingStatus 63 err error // Expected error output 64 }{ 65 { 66 "payload token invalid", 67 tokenb, 68 setBillingStatus(t, fid, 69 pi.SetBillingStatus{ 70 Token: "zzz", 71 Status: status, 72 Reason: "", 73 }), 74 pluginError(pi.ErrorCodeTokenInvalid), 75 }, 76 { 77 "payload token does not match cmd token", 78 tokenb, 79 setBillingStatus(t, fid, 80 pi.SetBillingStatus{ 81 Token: "da70d0766348340c", 82 Status: status, 83 Reason: "", 84 }), 85 pluginError(pi.ErrorCodeTokenInvalid), 86 }, 87 { 88 "invalid billing status", 89 tokenb, 90 pi.SetBillingStatus{ 91 Token: token, 92 Status: pi.BillingStatusT(9), 93 Reason: "", 94 PublicKey: publicKey, 95 Signature: signature, 96 }, 97 pluginError(pi.ErrorCodeBillingStatusInvalid), 98 }, 99 { 100 "signature is not hex", 101 tokenb, 102 pi.SetBillingStatus{ 103 Token: token, 104 Status: status, 105 Reason: "", 106 PublicKey: publicKey, 107 Signature: "zzz", 108 }, 109 pluginError(pi.ErrorCodeSignatureInvalid), 110 }, 111 { 112 "signature is the wrong size", 113 tokenb, 114 pi.SetBillingStatus{ 115 Token: token, 116 Status: status, 117 Reason: "", 118 PublicKey: publicKey, 119 Signature: "123456", 120 }, 121 pluginError(pi.ErrorCodeSignatureInvalid), 122 }, 123 { 124 "signature is wrong", 125 tokenb, 126 pi.SetBillingStatus{ 127 Token: token, 128 Status: status, 129 Reason: "", 130 PublicKey: publicKey, 131 Signature: signatureIsWrong, 132 }, 133 pluginError(pi.ErrorCodeSignatureInvalid), 134 }, 135 { 136 "public key is not a hex", 137 tokenb, 138 pi.SetBillingStatus{ 139 Token: token, 140 Status: status, 141 Reason: "", 142 PublicKey: "", 143 Signature: signature, 144 }, 145 pluginError(pi.ErrorCodePublicKeyInvalid), 146 }, 147 { 148 "public key is the wrong length", 149 tokenb, 150 pi.SetBillingStatus{ 151 Token: token, 152 Status: status, 153 Reason: "", 154 PublicKey: "123456", 155 Signature: signature, 156 }, 157 pluginError(pi.ErrorCodePublicKeyInvalid), 158 }, 159 { 160 "set billing status to close without a reason", 161 tokenb, 162 pi.SetBillingStatus{ 163 Token: token, 164 Status: pi.BillingStatusClosed, 165 Reason: "", 166 PublicKey: publicKey, 167 Signature: signatureWithClosed, 168 }, 169 pluginError(pi.ErrorCodeBillingStatusChangeNotAllowed), 170 }, 171 } 172 173 // Run tests 174 for _, tc := range tests { 175 t.Run(tc.name, func(t *testing.T) { 176 // Setup command payload 177 b, err := json.Marshal(tc.sbs) 178 if err != nil { 179 t.Fatal(err) 180 } 181 payload := string(b) 182 183 // Decode the expected error into a PluginError. If 184 // an error is being returned it should always be a 185 // PluginError. 186 var wantErrorCode pi.ErrorCodeT 187 if tc.err != nil { 188 var pe backend.PluginError 189 if !errors.As(tc.err, &pe) { 190 t.Fatalf("error is not a plugin error '%v'", tc.err) 191 } 192 wantErrorCode = pi.ErrorCodeT(pe.ErrorCode) 193 } 194 195 // Run test 196 _, err = p.cmdSetBillingStatus(tc.token, payload) 197 switch { 198 case tc.err != nil && err == nil: 199 // Wanted an error but didn't get one 200 t.Errorf("want error '%v', got nil", 201 pi.ErrorCodes[wantErrorCode]) 202 return 203 204 case tc.err == nil && err != nil: 205 // Wanted success but got an error 206 t.Errorf("want error nil, got '%v'", err) 207 return 208 209 case tc.err != nil && err != nil: 210 // Wanted an error and got an error. Verify that it's 211 // the correct error. All errors should be backend 212 // plugin errors. 213 var gotErr backend.PluginError 214 if !errors.As(err, &gotErr) { 215 t.Errorf("want plugin error, got '%v'", err) 216 return 217 } 218 if pi.PluginID != gotErr.PluginID { 219 t.Errorf("want plugin error with plugin ID '%v', got '%v'", 220 pi.PluginID, gotErr.PluginID) 221 return 222 } 223 224 gotErrorCode := pi.ErrorCodeT(gotErr.ErrorCode) 225 if wantErrorCode != gotErrorCode { 226 t.Errorf("want error '%v', got '%v'", 227 pi.ErrorCodes[wantErrorCode], 228 pi.ErrorCodes[gotErrorCode]) 229 } 230 231 // Success; continue to next test 232 return 233 234 case tc.err == nil && err == nil: 235 // Success; continue to next test 236 return 237 } 238 }) 239 } 240 } 241 242 func TestCmdSummary(t *testing.T) { 243 // Setup pi plugin 244 p, cleanup := newTestPiPlugin(t) 245 defer cleanup() 246 247 // Setup tests 248 var tests = []struct { 249 name string // Test name 250 token string 251 propStatus pi.PropStatusT // expected proposal status 252 }{ 253 { 254 name: string(pi.PropStatusUnvettedAbandoned), 255 token: "45154fb45664714b", 256 propStatus: pi.PropStatusUnvettedAbandoned, 257 }, 258 { 259 name: string(pi.PropStatusUnvettedCensored), 260 token: "45154fb45664714a", 261 propStatus: pi.PropStatusUnvettedCensored, 262 }, 263 { 264 name: string(pi.PropStatusAbandoned), 265 token: "45154fb45664714c", 266 propStatus: pi.PropStatusAbandoned, 267 }, 268 { 269 name: string(pi.PropStatusCensored), 270 token: "45154fb45664714d", 271 propStatus: pi.PropStatusCensored, 272 }, 273 { 274 name: string(pi.PropStatusAbandoned), 275 token: "45154fb45664714e", 276 propStatus: pi.PropStatusAbandoned, 277 }, 278 { 279 name: string(pi.PropStatusCensored), 280 token: "45154fb45664714f", 281 propStatus: pi.PropStatusCensored, 282 }, 283 } 284 285 // Run tests 286 for _, tc := range tests { 287 t.Run(tc.name, func(t *testing.T) { 288 // Decode string token 289 bt, err := hex.DecodeString(tc.token) 290 if err != nil { 291 t.Fatal(err) 292 } 293 294 // Cache final status 295 p.statuses.set(tc.token, statusEntry{ 296 propStatus: tc.propStatus, 297 }) 298 299 // Run test 300 r, err := p.cmdSummary(bt) 301 if err != nil { 302 // Unexpected error 303 t.Fatal(err) 304 } 305 306 // Unmarshal command reply 307 var sr pi.SummaryReply 308 err = json.Unmarshal([]byte(r), &sr) 309 if err != nil { 310 t.Fatal(err) 311 } 312 313 // Check if received proposal status euqal to the expected. 314 if sr.Summary.Status != tc.propStatus { 315 t.Errorf("want proposal status %v, got '%v'", tc.propStatus, 316 sr.Summary.Status) 317 } 318 }) 319 } 320 321 } 322 323 // setBillingStatus uses the provided arguments to return a SetBillingStatus 324 // with a valid PublicKey and Signature. 325 func setBillingStatus(t *testing.T, fid *identity.FullIdentity, sbs pi.SetBillingStatus) pi.SetBillingStatus { 326 t.Helper() 327 328 msg := sbs.Token + strconv.Itoa(int(sbs.Status)) + sbs.Reason 329 sig := fid.SignMessage([]byte(msg)) 330 331 return pi.SetBillingStatus{ 332 Token: sbs.Token, 333 Status: sbs.Status, 334 Reason: sbs.Reason, 335 PublicKey: fid.Public.String(), 336 Signature: hex.EncodeToString(sig[:]), 337 } 338 } 339 340 // pluginError returns a backend PluginError for the provided pi ErrorCodeT. 341 func pluginError(e pi.ErrorCodeT) error { 342 return backend.PluginError{ 343 PluginID: pi.PluginID, 344 ErrorCode: uint32(e), 345 } 346 }