github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/test/integration/push_int_test.go (about)

     1  package integration
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"runtime"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/ActiveState/cli/internal/testhelpers/suite"
    12  	"github.com/ActiveState/termtest"
    13  
    14  	"github.com/ActiveState/cli/internal/constants"
    15  	"github.com/ActiveState/cli/internal/strutils"
    16  	"github.com/ActiveState/cli/internal/testhelpers/e2e"
    17  	"github.com/ActiveState/cli/internal/testhelpers/tagsuite"
    18  	"github.com/ActiveState/cli/pkg/project"
    19  	"github.com/ActiveState/cli/pkg/projectfile"
    20  )
    21  
    22  type PushIntegrationTestSuite struct {
    23  	tagsuite.Suite
    24  	username string
    25  
    26  	// some variables re-used between tests
    27  	baseProject   string
    28  	language      string
    29  	languageFull  string
    30  	extraPackage  string
    31  	extraPackage2 string
    32  }
    33  
    34  func (suite *PushIntegrationTestSuite) SetupSuite() {
    35  	suite.username = e2e.PersistentUsername
    36  	suite.language = "perl"
    37  	suite.languageFull = "perl@5.32.0"
    38  	suite.baseProject = "ActiveState-CLI/Perl-5.32"
    39  	suite.extraPackage = "JSON"
    40  	suite.extraPackage2 = "DateTime"
    41  	if runtime.GOOS == "darwin" {
    42  		suite.language = "python"
    43  		suite.languageFull = "python"
    44  		suite.baseProject = "ActiveState-CLI/small-python"
    45  		suite.extraPackage = "trender"
    46  	}
    47  }
    48  
    49  func (suite *PushIntegrationTestSuite) TestInitAndPush() {
    50  	suite.OnlyRunForTags(tagsuite.Push)
    51  	ts := e2e.New(suite.T(), false)
    52  	defer ts.Close()
    53  	ts.LoginAsPersistentUser()
    54  	pname := strutils.UUID()
    55  	namespace := fmt.Sprintf("%s/%s", suite.username, pname)
    56  	cp := ts.Spawn(
    57  		"init",
    58  		"--language",
    59  		suite.languageFull,
    60  		namespace,
    61  		".",
    62  	)
    63  	cp.Expect("successfully initialized")
    64  	cp.ExpectExitCode(0)
    65  	ts.NotifyProjectCreated(suite.username, pname.String())
    66  
    67  	pjfilepath := filepath.Join(ts.Dirs.Work, constants.ConfigFileName)
    68  	suite.Require().FileExists(pjfilepath)
    69  
    70  	// Check that languages were reset
    71  	pj, err := project.FromPath(pjfilepath)
    72  	suite.Require().NoError(err)
    73  	suite.Require().NotEmpty(ts.CommitID(), "commitID was not set after running push for project creation")
    74  	suite.Require().NotEmpty(pj.BranchName(), "branch was not set after running push for project creation")
    75  
    76  	// ensure that we are logged out
    77  	cp = ts.Spawn(tagsuite.Auth, "logout")
    78  	cp.ExpectExitCode(0)
    79  
    80  	cp = ts.SpawnWithOpts(e2e.OptArgs("install", suite.extraPackage))
    81  	switch runtime.GOOS {
    82  	case "darwin":
    83  		cp.ExpectRe("added|being built", termtest.OptExpectTimeout(60*time.Second)) // while cold storage is off
    84  		cp.Wait()
    85  	default:
    86  		cp.Expect("added", termtest.OptExpectTimeout(60*time.Second))
    87  		cp.ExpectExitCode(0)
    88  	}
    89  
    90  	pj, err = project.FromPath(pjfilepath)
    91  	suite.Require().NoError(err)
    92  	if !strings.Contains(pj.Source().Project, fmt.Sprintf("/%s?", namespace)) {
    93  		suite.FailNow("project field should include project (not headless): " + pj.Source().Project)
    94  	}
    95  
    96  	ts.LoginAsPersistentUser()
    97  
    98  	cp = ts.SpawnWithOpts(e2e.OptArgs("push", namespace))
    99  	cp.Expect("Pushing to project")
   100  	cp.ExpectExitCode(0)
   101  }
   102  
   103  // Test pushing without permission, and choosing to create a new project
   104  func (suite *PushIntegrationTestSuite) TestPush_NoPermission_NewProject() {
   105  	if runtime.GOOS == "windows" {
   106  		suite.T().Skip("Skipped on Windows for now because SendKeyDown() doesnt work (regardless of bash/cmd)")
   107  	}
   108  
   109  	suite.OnlyRunForTags(tagsuite.Push)
   110  	ts := e2e.New(suite.T(), false)
   111  	defer ts.Close()
   112  	user := ts.CreateNewUser()
   113  	pname := strutils.UUID()
   114  
   115  	cp := ts.SpawnWithOpts(e2e.OptArgs("activate", suite.baseProject, "--path", ts.Dirs.Work))
   116  	cp.Expect("Activated", termtest.OptExpectTimeout(40*time.Second))
   117  	cp.ExpectInput(termtest.OptExpectTimeout(10 * time.Second))
   118  	cp.SendLine("exit")
   119  	cp.ExpectExitCode(0)
   120  
   121  	cp = ts.SpawnWithOpts(e2e.OptArgs("install", suite.extraPackage))
   122  	switch runtime.GOOS {
   123  	case "darwin":
   124  		cp.ExpectRe("added|being built", termtest.OptExpectTimeout(60*time.Second)) // while cold storage is off
   125  		cp.Wait()
   126  	default:
   127  		cp.Expect("added", termtest.OptExpectTimeout(60*time.Second))
   128  		cp.ExpectExitCode(0)
   129  	}
   130  
   131  	pjfilepath := filepath.Join(ts.Dirs.Work, constants.ConfigFileName)
   132  	pjfile, err := projectfile.Parse(pjfilepath)
   133  	suite.Require().NoError(err)
   134  	suite.Require().Contains(pjfile.Project, suite.baseProject)
   135  
   136  	cp = ts.SpawnWithOpts(e2e.OptArgs("push"))
   137  	cp.Expect("not authorized")
   138  	cp.Expect("(Y/n)")
   139  	cp.SendLine("y")
   140  	cp.Expect("Who would you like the owner of this project to be?")
   141  	cp.SendEnter()
   142  	cp.Expect("What would you like the name of this project to be?")
   143  	cp.SendKeyDown()
   144  	cp.Expect("> Other")
   145  	cp.SendEnter()
   146  	cp.Expect(">")
   147  	cp.SendLine(pname.String())
   148  	cp.Expect("Project created")
   149  	cp.ExpectExitCode(0)
   150  	// Note: no need for ts.NotifyProjectCreated because newly created users and their projects are
   151  	// auto-cleaned by e2e.
   152  
   153  	pjfile, err = projectfile.Parse(pjfilepath)
   154  	suite.Require().NoError(err)
   155  	suite.Require().Contains(pjfile.Project, user.Username)
   156  	suite.Require().Contains(pjfile.Project, pname.String())
   157  }
   158  
   159  func (suite *PushIntegrationTestSuite) TestCarlisle() {
   160  	suite.OnlyRunForTags(tagsuite.Push, tagsuite.Carlisle)
   161  	ts := e2e.New(suite.T(), false)
   162  	defer ts.Close()
   163  	pname := strutils.UUID()
   164  	namespace := fmt.Sprintf("%s/%s", suite.username, pname)
   165  
   166  	wd := filepath.Join(ts.Dirs.Work, namespace)
   167  	cp := ts.SpawnWithOpts(
   168  		e2e.OptArgs(
   169  			"activate", suite.baseProject,
   170  			"--path", wd),
   171  		e2e.OptAppendEnv(constants.DisableRuntime+"=false"),
   172  	)
   173  	// The activestate.yaml on Windows runs custom activation to set shortcuts and file associations.
   174  	cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt)
   175  	cp.SendLine("exit")
   176  	cp.ExpectExitCode(0)
   177  
   178  	// ensure that we are logged out
   179  	cp = ts.Spawn(tagsuite.Auth, "logout")
   180  	cp.ExpectExitCode(0)
   181  
   182  	// anonymous commit
   183  	cp = ts.SpawnWithOpts(e2e.OptArgs(
   184  		"install", suite.extraPackage),
   185  		e2e.OptWD(wd),
   186  		e2e.OptAppendEnv(constants.DisableRuntime+"=false"),
   187  	)
   188  	switch runtime.GOOS {
   189  	case "darwin":
   190  		cp.ExpectRe("added|being built", e2e.RuntimeSourcingTimeoutOpt) // while cold storage is off
   191  		cp.Wait()
   192  	default:
   193  		cp.Expect("added", e2e.RuntimeSourcingTimeoutOpt)
   194  		cp.ExpectExitCode(0)
   195  	}
   196  
   197  	prj, err := project.FromPath(filepath.Join(wd, constants.ConfigFileName))
   198  	suite.Require().NoError(err, "Could not parse project file")
   199  	suite.Assert().False(prj.IsHeadless(), "project should NOT be headless: URL is %s", prj.URL())
   200  
   201  	ts.LoginAsPersistentUser()
   202  
   203  	cp = ts.SpawnWithOpts(e2e.OptArgs("push", namespace), e2e.OptWD(wd))
   204  	cp.Expect("continue? (Y/n)")
   205  	cp.SendLine("y")
   206  	cp.Expect("Project created")
   207  	cp.ExpectExitCode(0)
   208  	ts.NotifyProjectCreated(suite.username, pname.String())
   209  }
   210  
   211  func (suite *PushIntegrationTestSuite) TestPush_NoProject() {
   212  	suite.OnlyRunForTags(tagsuite.Push)
   213  
   214  	ts := e2e.New(suite.T(), false)
   215  	defer ts.Close()
   216  
   217  	ts.LoginAsPersistentUser()
   218  	cp := ts.SpawnWithOpts(e2e.OptArgs("push"))
   219  	cp.Expect("No project found")
   220  	cp.ExpectExitCode(1)
   221  	ts.IgnoreLogErrors()
   222  
   223  	if strings.Count(cp.Snapshot(), " x ") != 1 {
   224  		suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot())
   225  	}
   226  }
   227  
   228  func (suite *PushIntegrationTestSuite) TestPush_NoAuth() {
   229  	suite.OnlyRunForTags(tagsuite.Push)
   230  
   231  	ts := e2e.New(suite.T(), false)
   232  	defer ts.Close()
   233  
   234  	ts.PrepareProject("ActiveState-CLI/cli", "882ae76e-fbb7-4989-acc9-9a8b87d49388")
   235  
   236  	cp := ts.SpawnWithOpts(e2e.OptArgs("push"))
   237  	cp.Expect("you need to be authenticated")
   238  	cp.ExpectExitCode(1)
   239  	ts.IgnoreLogErrors()
   240  
   241  	if strings.Count(cp.Snapshot(), " x ") != 1 {
   242  		suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot())
   243  	}
   244  }
   245  
   246  func (suite *PushIntegrationTestSuite) TestPush_NoChanges() {
   247  	suite.OnlyRunForTags(tagsuite.Push)
   248  
   249  	ts := e2e.New(suite.T(), false)
   250  	defer ts.Close()
   251  
   252  	cp := ts.SpawnWithOpts(e2e.OptArgs("checkout", "ActiveState-CLI/small-python", "."))
   253  	cp.ExpectExitCode(0)
   254  
   255  	ts.LoginAsPersistentUser()
   256  	cp = ts.SpawnWithOpts(e2e.OptArgs("push"))
   257  	cp.Expect("no local changes to push")
   258  	cp.ExpectExitCode(1)
   259  	ts.IgnoreLogErrors()
   260  
   261  	if strings.Count(cp.Snapshot(), " x ") != 1 {
   262  		suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot())
   263  	}
   264  }
   265  
   266  func (suite *PushIntegrationTestSuite) TestPush_NameInUse() {
   267  	suite.OnlyRunForTags(tagsuite.Push)
   268  
   269  	ts := e2e.New(suite.T(), false)
   270  	defer ts.Close()
   271  
   272  	// Source project we do not have access to
   273  	ts.PrepareProject("ActiveState-Test-DevNull/push-error-test", "2aa0b8fa-04e2-4079-bde1-d46764e3cb53")
   274  
   275  	ts.LoginAsPersistentUser()
   276  	// Target project already exists
   277  	cp := ts.SpawnWithOpts(e2e.OptArgs("push", "-n", "ActiveState-CLI/push-error-test"))
   278  	cp.Expect("already in use")
   279  	cp.ExpectExitCode(1)
   280  	ts.IgnoreLogErrors()
   281  
   282  	if strings.Count(cp.Snapshot(), " x ") != 1 {
   283  		suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot())
   284  	}
   285  }
   286  
   287  func (suite *PushIntegrationTestSuite) TestPush_Aborted() {
   288  	// Skipped for now due to DX-2244
   289  	suite.T().Skip("Confirming prompt with N not working, must fix first")
   290  
   291  	suite.OnlyRunForTags(tagsuite.Push)
   292  
   293  	ts := e2e.New(suite.T(), true)
   294  	defer ts.Close()
   295  
   296  	// Source project we do not have access to
   297  	ts.PrepareProject("ActiveState-Test-DevNull/push-error-test", "2aa0b8fa-04e2-4079-bde1-d46764e3cb53")
   298  
   299  	ts.LoginAsPersistentUser()
   300  	// Target project already exists
   301  	cp := ts.SpawnWithOpts(e2e.OptArgs("push"))
   302  	cp.Expect("Would you like to create a new project")
   303  	cp.SendLine("n")
   304  	cp.Expect("Project creation aborted by user", termtest.OptExpectTimeout(5*time.Second))
   305  	cp.ExpectExitCode(1)
   306  	ts.IgnoreLogErrors()
   307  
   308  	if strings.Count(cp.Snapshot(), " x ") != 1 {
   309  		suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot())
   310  	}
   311  }
   312  
   313  func (suite *PushIntegrationTestSuite) TestPush_InvalidHistory() {
   314  	suite.OnlyRunForTags(tagsuite.Push)
   315  
   316  	ts := e2e.New(suite.T(), true)
   317  	defer ts.Close()
   318  
   319  	// Note the commit we're using here is for another project, in order to repro the error
   320  	ts.PrepareProject("ActiveState-CLI/small-python", "dbc0415e-91e8-407b-ad36-1de0cc5c0cbb")
   321  
   322  	ts.LoginAsPersistentUser()
   323  	// Target project already exists
   324  	cp := ts.SpawnWithOpts(e2e.OptArgs("push", "ActiveState-CLI/push-error-test"))
   325  	cp.Expect("commit history does not match")
   326  	cp.ExpectExitCode(1)
   327  	ts.IgnoreLogErrors()
   328  
   329  	if strings.Count(cp.Snapshot(), " x ") != 1 {
   330  		suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot())
   331  	}
   332  }
   333  
   334  func (suite *PushIntegrationTestSuite) TestPush_PullNeeded() {
   335  	suite.OnlyRunForTags(tagsuite.Push)
   336  
   337  	ts := e2e.New(suite.T(), true)
   338  	defer ts.Close()
   339  
   340  	ts.PrepareProject("ActiveState-CLI/push-error-test", "899c9b4c-d28d-441a-9c28-c84819ba8b1a")
   341  
   342  	ts.LoginAsPersistentUser()
   343  	// Target project already exists
   344  	cp := ts.SpawnWithOpts(e2e.OptArgs("push"))
   345  	cp.Expect("changes available that need to be merged")
   346  	cp.ExpectExitCode(1)
   347  	ts.IgnoreLogErrors()
   348  
   349  	if strings.Count(cp.Snapshot(), " x ") != 1 {
   350  		suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot())
   351  	}
   352  }
   353  
   354  func (suite *PushIntegrationTestSuite) TestPush_Outdated() {
   355  	suite.OnlyRunForTags(tagsuite.Push)
   356  	unPushedCommit := "882ae76e-fbb7-4989-acc9-9a8b87d49388"
   357  
   358  	ts := e2e.New(suite.T(), false)
   359  	defer ts.Close()
   360  
   361  	ts.PrepareProject("ActiveState-CLI/cli", unPushedCommit)
   362  
   363  	ts.LoginAsPersistentUser()
   364  	cp := ts.SpawnWithOpts(e2e.OptArgs("push"))
   365  	cp.Expect("Your project has new changes available")
   366  	cp.ExpectExitCode(1)
   367  	ts.IgnoreLogErrors()
   368  }
   369  
   370  func TestPushIntegrationTestSuite(t *testing.T) {
   371  	suite.Run(t, new(PushIntegrationTestSuite))
   372  }