github.com/decred/politeia@v1.4.0/politeiawww/legacy/testing.go (about)

     1  // Copyright (c) 2017-2020 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 legacy
     6  
     7  import (
     8  	"encoding/hex"
     9  	"errors"
    10  	"os"
    11  	"path/filepath"
    12  	"testing"
    13  
    14  	"github.com/decred/dcrd/chaincfg/v3"
    15  	"github.com/decred/politeia/politeiad/api/v1/identity"
    16  	cms "github.com/decred/politeia/politeiawww/api/cms/v1"
    17  	www "github.com/decred/politeia/politeiawww/api/www/v1"
    18  	"github.com/decred/politeia/politeiawww/config"
    19  	"github.com/decred/politeia/politeiawww/legacy/mail"
    20  	"github.com/decred/politeia/politeiawww/legacy/sessions"
    21  	"github.com/decred/politeia/politeiawww/legacy/user"
    22  	"github.com/decred/politeia/politeiawww/legacy/user/localdb"
    23  	"github.com/decred/politeia/politeiawww/logger"
    24  	"github.com/decred/politeia/util"
    25  	"github.com/google/uuid"
    26  	"github.com/gorilla/mux"
    27  )
    28  
    29  // errToStr returns the string representation of the error. If the error is a
    30  // UserError then the human readable error message is returned instead of the
    31  // error code.
    32  func errToStr(e error) string {
    33  	if e == nil {
    34  		return "nil"
    35  	}
    36  
    37  	var userErr www.UserError
    38  	if errors.As(e, &userErr) {
    39  		return www.ErrorStatus[userErr.ErrorCode]
    40  	}
    41  
    42  	return e.Error()
    43  }
    44  
    45  // newUser creates a new user using randomly generated user credentials and
    46  // inserts the user into the database.  The user details and the full user
    47  // identity are returned.
    48  func newUser(t *testing.T, p *Politeiawww, isVerified, isAdmin bool) (*user.User, *identity.FullIdentity) {
    49  	t.Helper()
    50  
    51  	// Generate random bytes to be used as user credentials
    52  	r, err := util.Random(int(www.PolicyMinPasswordLength))
    53  	if err != nil {
    54  		t.Fatalf("%v", err)
    55  	}
    56  
    57  	// Setup user
    58  	pass, err := p.hashPassword(hex.EncodeToString(r))
    59  	if err != nil {
    60  		t.Fatalf("%v", err)
    61  	}
    62  	tokenb, expiry, err := newVerificationTokenAndExpiry()
    63  	if err != nil {
    64  		t.Fatalf("%v", err)
    65  	}
    66  	u := user.User{
    67  		ID:                        uuid.New(),
    68  		Admin:                     isAdmin,
    69  		Email:                     hex.EncodeToString(r) + "@example.com",
    70  		Username:                  hex.EncodeToString(r),
    71  		HashedPassword:            pass,
    72  		NewUserVerificationToken:  tokenb,
    73  		NewUserVerificationExpiry: expiry,
    74  	}
    75  	fid, err := identity.New()
    76  	if err != nil {
    77  		t.Fatalf("%v", err)
    78  	}
    79  	pubkey := hex.EncodeToString(fid.Public.Key[:])
    80  	id, err := user.NewIdentity(pubkey)
    81  	if err != nil {
    82  		t.Fatalf("%v", err)
    83  	}
    84  	err = u.AddIdentity(*id)
    85  	if err != nil {
    86  		t.Fatalf("%v", err)
    87  	}
    88  	if isVerified {
    89  		u.NewUserVerificationToken = nil
    90  		u.NewUserVerificationExpiry = 0
    91  		err := u.ActivateIdentity(id.Key[:])
    92  		if err != nil {
    93  			t.Fatalf("%v", err)
    94  		}
    95  	}
    96  
    97  	// Add user to database
    98  	err = p.db.UserNew(u)
    99  	if err != nil {
   100  		t.Fatalf("%v", err)
   101  	}
   102  
   103  	// Add the user to the politeiawww in-memory [email]userID
   104  	// cache. Since the userID is generated in the database layer
   105  	// we need to lookup the user in order to get the userID.
   106  	usr, err := p.db.UserGetByUsername(u.Username)
   107  	if err != nil {
   108  		t.Fatalf("%v", err)
   109  	}
   110  	p.setUserEmailsCache(usr.Email, usr.ID)
   111  
   112  	// Add paywall info to the user record
   113  	err = p.generateNewUserPaywall(usr)
   114  	if err != nil {
   115  		t.Fatalf("%v", err)
   116  	}
   117  
   118  	// Lookup user record one more time so that
   119  	// we return a user object with the paywall
   120  	// details filled in.
   121  	usr, err = p.db.UserGetByUsername(u.Username)
   122  	if err != nil {
   123  		t.Fatalf("%v", err)
   124  	}
   125  
   126  	return usr, fid
   127  }
   128  
   129  // newCMSUser creates a new user using randomly generated user credentials and
   130  // inserts the user into the database.  The user details and the full user
   131  // identity are returned.
   132  func newCMSUser(t *testing.T, p *Politeiawww, isAdmin bool, setGithubname bool, domain cms.DomainTypeT, contractorType cms.ContractorTypeT) *user.CMSUser {
   133  	t.Helper()
   134  
   135  	// Generate random bytes to be used as user credentials
   136  	r, err := util.Random(int(www.PolicyMinPasswordLength))
   137  	if err != nil {
   138  		t.Fatalf("%v", err)
   139  	}
   140  
   141  	u := user.NewCMSUser{
   142  		Email:                     hex.EncodeToString(r) + "@example.com",
   143  		Username:                  hex.EncodeToString(r),
   144  		NewUserVerificationToken:  nil,
   145  		NewUserVerificationExpiry: 0,
   146  		ContractorType:            int(cms.ContractorTypeDirect),
   147  	}
   148  
   149  	payload, err := user.EncodeNewCMSUser(u)
   150  	if err != nil {
   151  		t.Errorf("unable to encode new user %v", err)
   152  	}
   153  	pc := user.PluginCommand{
   154  		ID:      user.CMSPluginID,
   155  		Command: user.CmdNewCMSUser,
   156  		Payload: string(payload),
   157  	}
   158  	_, err = p.db.PluginExec(pc)
   159  	if err != nil {
   160  		t.Errorf("unable to execute new cms user db request %v", err)
   161  	}
   162  
   163  	usr, err := p.db.UserGetByUsername(u.Username)
   164  	if err != nil {
   165  		t.Fatalf("error getting user by username %v", err)
   166  	}
   167  	p.setUserEmailsCache(usr.Email, usr.ID)
   168  
   169  	if isAdmin {
   170  		usr.Admin = true
   171  		err := p.db.UserUpdate(*usr)
   172  		if err != nil {
   173  			t.Fatalf("error updating user for admin %v", err)
   174  		}
   175  	}
   176  
   177  	uu := user.UpdateCMSUser{
   178  		ID:             usr.ID,
   179  		Domain:         int(domain),
   180  		ContractorType: int(contractorType),
   181  	}
   182  	if setGithubname {
   183  		uu.GitHubName = "testGithub"
   184  	}
   185  	payload, err = user.EncodeUpdateCMSUser(uu)
   186  	if err != nil {
   187  		t.Errorf("unable to encode new user %v", err)
   188  	}
   189  	pc = user.PluginCommand{
   190  		ID:      user.CMSPluginID,
   191  		Command: user.CmdUpdateCMSUser,
   192  		Payload: string(payload),
   193  	}
   194  	_, err = p.db.PluginExec(pc)
   195  	if err != nil {
   196  		t.Errorf("unable to execute update cms user db request %v", err)
   197  	}
   198  
   199  	// Add the user to the politeiawww in-memory [email]userID
   200  	// cache. Since the userID is generated in the database layer
   201  	// we need to lookup the user in order to get the userID.
   202  	usr, err = p.db.UserGetByUsername(u.Username)
   203  	if err != nil {
   204  		t.Fatalf("%v", err)
   205  	}
   206  	p.setUserEmailsCache(usr.Email, usr.ID)
   207  
   208  	cmsUser, err := p.getCMSUserByIDRaw(usr.ID.String())
   209  	if err != nil {
   210  		t.Fatalf("%v", err)
   211  	}
   212  
   213  	return cmsUser
   214  }
   215  
   216  // newTestPoliteiawww returns a new politeiawww context that is setup for
   217  // testing and a closure that cleans up the test environment when invoked.
   218  func newTestPoliteiawww(t *testing.T) (*Politeiawww, func()) {
   219  	t.Helper()
   220  
   221  	// Make a temp directory for test data. Temp directory
   222  	// is removed in the returned closure.
   223  	dataDir, err := os.MkdirTemp("", "politeiawww.test")
   224  	if err != nil {
   225  		t.Fatalf("open tmp dir: %v", err)
   226  	}
   227  
   228  	// Setup logging
   229  	// initLogRotator(filepath.Join(dataDir, "politeiawww.test.log"))
   230  	// setLogLevels("off")
   231  
   232  	// Setup config
   233  	xpub := "tpubVobLtToNtTq6TZNw4raWQok35PRPZou53vegZqNubtBTJMMFm" +
   234  		"uMpWybFCfweJ52N8uZJPZZdHE5SRnBBuuRPfC5jdNstfKjiAs8JtbYG9jx"
   235  	fid, err := identity.New()
   236  	if err != nil {
   237  		t.Fatal(err)
   238  	}
   239  	cfg := &config.Config{
   240  		DataDir: dataDir,
   241  		TestNet: true,
   242  		LegacyConfig: config.LegacyConfig{
   243  			PaywallAmount:   1e7,
   244  			PaywallXpub:     xpub,
   245  			VoteDurationMin: 2016,
   246  			VoteDurationMax: 4032,
   247  		},
   248  		Identity: &fid.Public,
   249  	}
   250  
   251  	// Setup database
   252  	db, err := localdb.New(filepath.Join(cfg.DataDir, "localdb"))
   253  	if err != nil {
   254  		t.Fatalf("setup database: %v", err)
   255  	}
   256  
   257  	// Setup mail client
   258  	mailClient, err := mail.NewClient("", "", "", "", "", false, 0, db)
   259  	if err != nil {
   260  		t.Fatal(err)
   261  	}
   262  
   263  	// Setup sessions
   264  	cookieKey, err := util.Random(32)
   265  	if err != nil {
   266  		t.Fatalf("create cookie key: %v", err)
   267  	}
   268  
   269  	// Setup politeiawww context
   270  	p := Politeiawww{
   271  		cfg:             cfg,
   272  		params:          chaincfg.TestNet3Params(),
   273  		router:          mux.NewRouter(),
   274  		auth:            mux.NewRouter(),
   275  		sessions:        sessions.New(db, cookieKey),
   276  		mail:            mailClient,
   277  		db:              db,
   278  		test:            true,
   279  		userEmails:      make(map[string]uuid.UUID),
   280  		userPaywallPool: make(map[uuid.UUID]paywallPoolMember),
   281  	}
   282  
   283  	// Setup routes
   284  	p.setUserWWWRoutes()
   285  
   286  	// The cleanup is handled using a closure so that the temp dir
   287  	// can be deleted using the local variable and not cfg.DataDir.
   288  	// Using cfg.DataDir could be misused and lead to the deletion
   289  	// of an unintended directory.
   290  	return &p, func() {
   291  		t.Helper()
   292  
   293  		err := db.Close()
   294  		if err != nil {
   295  			t.Fatalf("close db: %v", err)
   296  		}
   297  
   298  		/*
   299  			err = logRotator.Close()
   300  			if err != nil {
   301  				t.Fatalf("close log rotator: %v", err)
   302  			}
   303  		*/
   304  
   305  		err = os.RemoveAll(dataDir)
   306  		if err != nil {
   307  			t.Fatalf("remove tmp dir: %v", err)
   308  		}
   309  	}
   310  }
   311  
   312  // newTestCMSwww returns a new cmswww context that is setup for
   313  // testing and a closure that cleans up the test environment when invoked.
   314  func newTestCMSwww(t *testing.T) (*Politeiawww, func()) {
   315  	t.Helper()
   316  
   317  	// Make a temp directory for test data. Temp directory
   318  	// is removed in the returned closure.
   319  	dataDir, err := os.MkdirTemp("", "cmswww.test")
   320  	if err != nil {
   321  		t.Fatalf("open tmp dir: %v", err)
   322  	}
   323  
   324  	// Turn logging off
   325  	logger.InitLogRotator(filepath.Join(dataDir, "cmswww.test.log"))
   326  	logger.SetLogLevels("off")
   327  
   328  	// Setup config
   329  	xpub := "tpubVobLtToNtTq6TZNw4raWQok35PRPZou53vegZqNubtBTJMMFm" +
   330  		"uMpWybFCfweJ52N8uZJPZZdHE5SRnBBuuRPfC5jdNstfKjiAs8JtbYG9jx"
   331  	cfg := &config.Config{
   332  		DataDir: dataDir,
   333  		TestNet: true,
   334  		LegacyConfig: config.LegacyConfig{
   335  			PaywallAmount:   1e7,
   336  			PaywallXpub:     xpub,
   337  			VoteDurationMin: 2016,
   338  			VoteDurationMax: 4032,
   339  			Mode:            config.CMSWWWMode,
   340  		},
   341  	}
   342  
   343  	// Setup database
   344  	db, err := localdb.New(filepath.Join(cfg.DataDir, "localdb"))
   345  	if err != nil {
   346  		t.Fatalf("setup database: %v", err)
   347  	}
   348  
   349  	// Register cms userdb plugin
   350  	plugin := user.Plugin{
   351  		ID:      user.CMSPluginID,
   352  		Version: user.CMSPluginVersion,
   353  	}
   354  	err = db.RegisterPlugin(plugin)
   355  	if err != nil {
   356  		t.Fatalf("register userdb plugin: %v", err)
   357  	}
   358  
   359  	// Setup smtp
   360  	mailClient, err := mail.NewClient("", "", "", "", "", false, 0, db)
   361  	if err != nil {
   362  		t.Fatalf("setup SMTP: %v", err)
   363  	}
   364  
   365  	// Setup sessions
   366  	cookieKey, err := util.Random(32)
   367  	if err != nil {
   368  		t.Fatalf("create cookie key: %v", err)
   369  	}
   370  
   371  	// Create politeiawww context
   372  	p := Politeiawww{
   373  		cfg:             cfg,
   374  		db:              db,
   375  		params:          chaincfg.TestNet3Params(),
   376  		router:          mux.NewRouter(),
   377  		auth:            mux.NewRouter(),
   378  		sessions:        sessions.New(db, cookieKey),
   379  		mail:            mailClient,
   380  		test:            true,
   381  		userEmails:      make(map[string]uuid.UUID),
   382  		userPaywallPool: make(map[uuid.UUID]paywallPoolMember),
   383  	}
   384  
   385  	// Setup routes
   386  	p.setCMSWWWRoutes()
   387  	p.setCMSUserWWWRoutes()
   388  
   389  	// The cleanup is handled using a closure so that the temp dir
   390  	// can be deleted using the local variable and not cfg.DataDir.
   391  	// Using cfg.DataDir could be misused and lead to the deletion
   392  	// of an unintended directory.
   393  	return &p, func() {
   394  		t.Helper()
   395  
   396  		err := db.Close()
   397  		if err != nil {
   398  			t.Fatalf("close db: %v", err)
   399  		}
   400  
   401  		/*
   402  			err = logRotator.Close()
   403  			if err != nil {
   404  				t.Fatalf("close log rotator: %v", err)
   405  			}
   406  		*/
   407  
   408  		err = os.RemoveAll(dataDir)
   409  		if err != nil {
   410  			t.Fatalf("remove tmp dir: %v", err)
   411  		}
   412  	}
   413  }