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  }