github.com/fastly/cli@v1.7.2-0.20240304164155-9d0f1d77c3bf/pkg/commands/authtoken/authtoken_test.go (about)

     1  package authtoken_test
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"testing"
     9  
    10  	"github.com/fastly/go-fastly/v9/fastly"
    11  
    12  	"github.com/fastly/cli/pkg/app"
    13  	"github.com/fastly/cli/pkg/global"
    14  	"github.com/fastly/cli/pkg/mock"
    15  	"github.com/fastly/cli/pkg/testutil"
    16  )
    17  
    18  func TestAuthTokenCreate(t *testing.T) {
    19  	args := testutil.Args
    20  	scenarios := []testutil.TestScenario{
    21  		{
    22  			Name:      "validate missing --password flag",
    23  			Args:      args("auth-token create"),
    24  			WantError: "error parsing arguments: required flag --password not provided",
    25  		},
    26  		{
    27  			Name: "validate CreateToken API error",
    28  			API: mock.API{
    29  				CreateTokenFn: func(i *fastly.CreateTokenInput) (*fastly.Token, error) {
    30  					return nil, testutil.Err
    31  				},
    32  			},
    33  			Args:      args("auth-token create --password secure --token 123"),
    34  			WantError: testutil.Err.Error(),
    35  		},
    36  		{
    37  			Name: "validate CreateToken API success with no flags",
    38  			API: mock.API{
    39  				CreateTokenFn: func(i *fastly.CreateTokenInput) (*fastly.Token, error) {
    40  					return &fastly.Token{
    41  						ExpiresAt:   &testutil.Date,
    42  						TokenID:     fastly.ToPointer("123"),
    43  						Name:        fastly.ToPointer("Example"),
    44  						Scope:       fastly.ToPointer(fastly.TokenScope("foobar")),
    45  						AccessToken: fastly.ToPointer("123abc"),
    46  					}, nil
    47  				},
    48  			},
    49  			Args:       args("auth-token create --password secure --token 123"),
    50  			WantOutput: "Created token '123abc' (name: Example, id: 123, scope: foobar, expires: 2021-06-15 23:00:00 +0000 UTC)",
    51  		},
    52  		{
    53  			Name: "validate CreateToken API success with all flags",
    54  			API: mock.API{
    55  				CreateTokenFn: func(i *fastly.CreateTokenInput) (*fastly.Token, error) {
    56  					return &fastly.Token{
    57  						ExpiresAt:   i.ExpiresAt,
    58  						TokenID:     fastly.ToPointer("123"),
    59  						Name:        i.Name,
    60  						Scope:       i.Scope,
    61  						AccessToken: fastly.ToPointer("123abc"),
    62  					}, nil
    63  				},
    64  			},
    65  			Args:       args("auth-token create --expires 2021-09-15T23:00:00Z --name Testing --password secure --scope purge_all --scope global:read --services a,b,c --token 123"),
    66  			WantOutput: "Created token '123abc' (name: Testing, id: 123, scope: purge_all global:read, expires: 2021-09-15 23:00:00 +0000 UTC)",
    67  		},
    68  	}
    69  
    70  	for testcaseIdx := range scenarios {
    71  		testcase := &scenarios[testcaseIdx]
    72  		t.Run(testcase.Name, func(t *testing.T) {
    73  			var stdout bytes.Buffer
    74  			app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
    75  				opts := testutil.MockGlobalData(testcase.Args, &stdout)
    76  				opts.APIClientFactory = mock.APIClient(testcase.API)
    77  				return opts, nil
    78  			}
    79  			err := app.Run(testcase.Args, nil)
    80  			testutil.AssertErrorContains(t, err, testcase.WantError)
    81  			testutil.AssertStringContains(t, stdout.String(), testcase.WantOutput)
    82  		})
    83  	}
    84  }
    85  
    86  func TestAuthTokenDelete(t *testing.T) {
    87  	args := testutil.Args
    88  	scenarios := []testutil.TestScenario{
    89  		{
    90  			Name:      "validate missing optional flags",
    91  			Args:      args("auth-token delete --token 123"),
    92  			WantError: "error parsing arguments: must provide either the --current, --file or --id flag",
    93  		},
    94  		{
    95  			Name: "validate DeleteTokenSelf API error with --current",
    96  			API: mock.API{
    97  				DeleteTokenSelfFn: func() error {
    98  					return testutil.Err
    99  				},
   100  			},
   101  			Args:      args("auth-token delete --current --token 123"),
   102  			WantError: testutil.Err.Error(),
   103  		},
   104  		{
   105  			Name: "validate BatchDeleteTokens API error with --file",
   106  			API: mock.API{
   107  				BatchDeleteTokensFn: func(i *fastly.BatchDeleteTokensInput) error {
   108  					return testutil.Err
   109  				},
   110  			},
   111  			Args:      args("auth-token delete --file ./testdata/tokens --token 123"),
   112  			WantError: testutil.Err.Error(),
   113  		},
   114  		{
   115  			Name: "validate DeleteToken API error with --id",
   116  			API: mock.API{
   117  				DeleteTokenFn: func(i *fastly.DeleteTokenInput) error {
   118  					return testutil.Err
   119  				},
   120  			},
   121  			Args:      args("auth-token delete --id 123 --token 123"),
   122  			WantError: testutil.Err.Error(),
   123  		},
   124  		{
   125  			Name: "validate DeleteTokenSelf API success with --current",
   126  			API: mock.API{
   127  				DeleteTokenSelfFn: func() error {
   128  					return nil
   129  				},
   130  			},
   131  			Args:       args("auth-token delete --current --token 123"),
   132  			WantOutput: "Deleted current token",
   133  		},
   134  		{
   135  			Name: "validate BatchDeleteTokens API success with --file",
   136  			API: mock.API{
   137  				BatchDeleteTokensFn: func(i *fastly.BatchDeleteTokensInput) error {
   138  					return nil
   139  				},
   140  			},
   141  			Args:       args("auth-token delete --file ./testdata/tokens --token 123"),
   142  			WantOutput: "Deleted tokens",
   143  		},
   144  		{
   145  			Name: "validate BatchDeleteTokens API success with --file and --verbose",
   146  			API: mock.API{
   147  				BatchDeleteTokensFn: func(i *fastly.BatchDeleteTokensInput) error {
   148  					return nil
   149  				},
   150  			},
   151  			Args:       args("auth-token delete --file ./testdata/tokens --token 123 --verbose"),
   152  			WantOutput: fileTokensOutput(),
   153  		},
   154  		{
   155  			Name: "validate DeleteToken API success with --id",
   156  			API: mock.API{
   157  				DeleteTokenFn: func(i *fastly.DeleteTokenInput) error {
   158  					return nil
   159  				},
   160  			},
   161  			Args:       args("auth-token delete --id 123 --token 123"),
   162  			WantOutput: "Deleted token '123'",
   163  		},
   164  	}
   165  
   166  	for testcaseIdx := range scenarios {
   167  		testcase := &scenarios[testcaseIdx]
   168  		t.Run(testcase.Name, func(t *testing.T) {
   169  			var stdout bytes.Buffer
   170  			app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
   171  				opts := testutil.MockGlobalData(testcase.Args, &stdout)
   172  				opts.APIClientFactory = mock.APIClient(testcase.API)
   173  				return opts, nil
   174  			}
   175  			err := app.Run(testcase.Args, nil)
   176  			testutil.AssertErrorContains(t, err, testcase.WantError)
   177  			testutil.AssertStringContains(t, stdout.String(), testcase.WantOutput)
   178  		})
   179  	}
   180  }
   181  
   182  func TestAuthTokenDescribe(t *testing.T) {
   183  	args := testutil.Args
   184  	scenarios := []testutil.TestScenario{
   185  		{
   186  			Name: "validate GetTokenSelf API error",
   187  			API: mock.API{
   188  				GetTokenSelfFn: func() (*fastly.Token, error) {
   189  					return nil, testutil.Err
   190  				},
   191  			},
   192  			Args:      args("auth-token describe --token 123"),
   193  			WantError: testutil.Err.Error(),
   194  		},
   195  		{
   196  			Name: "validate GetTokenSelf API success",
   197  			API: mock.API{
   198  				GetTokenSelfFn: getToken,
   199  			},
   200  			Args:       args("auth-token describe --token 123"),
   201  			WantOutput: describeTokenOutput(),
   202  		},
   203  	}
   204  
   205  	for testcaseIdx := range scenarios {
   206  		testcase := &scenarios[testcaseIdx]
   207  		t.Run(testcase.Name, func(t *testing.T) {
   208  			var stdout bytes.Buffer
   209  			app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
   210  				opts := testutil.MockGlobalData(testcase.Args, &stdout)
   211  				opts.APIClientFactory = mock.APIClient(testcase.API)
   212  				return opts, nil
   213  			}
   214  			err := app.Run(testcase.Args, nil)
   215  			testutil.AssertErrorContains(t, err, testcase.WantError)
   216  			testutil.AssertStringContains(t, stdout.String(), testcase.WantOutput)
   217  		})
   218  	}
   219  }
   220  
   221  func TestAuthTokenList(t *testing.T) {
   222  	args := testutil.Args
   223  	type ts struct {
   224  		testutil.TestScenario
   225  		SetEnv bool
   226  	}
   227  	scenarios := []ts{
   228  		{
   229  			TestScenario: testutil.TestScenario{
   230  				Name: "validate ListTokens API error",
   231  				API: mock.API{
   232  					ListTokensFn: func(_ *fastly.ListTokensInput) ([]*fastly.Token, error) {
   233  						return nil, testutil.Err
   234  					},
   235  				},
   236  				Args:      args("auth-token list"),
   237  				WantError: testutil.Err.Error(),
   238  			},
   239  		},
   240  		{
   241  			TestScenario: testutil.TestScenario{
   242  				Name: "validate ListCustomerTokens API error",
   243  				API: mock.API{
   244  					ListCustomerTokensFn: func(i *fastly.ListCustomerTokensInput) ([]*fastly.Token, error) {
   245  						return nil, testutil.Err
   246  					},
   247  				},
   248  				Args:      args("auth-token list --customer-id 123"),
   249  				WantError: testutil.Err.Error(),
   250  			},
   251  		},
   252  		{
   253  			TestScenario: testutil.TestScenario{
   254  				Name: "validate ListTokens API success",
   255  				API: mock.API{
   256  					ListTokensFn: listTokens,
   257  				},
   258  				Args:       args("auth-token list"),
   259  				WantOutput: listTokenOutputSummary(false),
   260  			},
   261  		},
   262  		{
   263  			TestScenario: testutil.TestScenario{
   264  				Name: "validate ListCustomerTokens API success",
   265  				API: mock.API{
   266  					ListCustomerTokensFn: listCustomerTokens,
   267  				},
   268  				Args:       args("auth-token list --customer-id 123"),
   269  				WantOutput: listTokenOutputSummary(false),
   270  			},
   271  		},
   272  		{
   273  			TestScenario: testutil.TestScenario{
   274  				Name: "validate ListCustomerTokens API success with env var",
   275  				API: mock.API{
   276  					ListCustomerTokensFn: listCustomerTokens,
   277  				},
   278  				Args:       args("auth-token list"),
   279  				WantOutput: listTokenOutputSummary(true),
   280  			},
   281  			SetEnv: true,
   282  		},
   283  		{
   284  			TestScenario: testutil.TestScenario{
   285  				Name: "validate --verbose flag",
   286  				API: mock.API{
   287  					ListTokensFn: listTokens,
   288  				},
   289  				Args:       args("auth-token list --verbose"),
   290  				WantOutput: listTokenOutputVerbose(),
   291  			},
   292  		},
   293  	}
   294  
   295  	for testcaseIdx := range scenarios {
   296  		testcase := &scenarios[testcaseIdx]
   297  		t.Run(testcase.Name, func(t *testing.T) {
   298  			if testcase.SetEnv {
   299  				if err := os.Setenv("FASTLY_CUSTOMER_ID", "123"); err != nil {
   300  					t.Fatal(err)
   301  				}
   302  				defer func() {
   303  					if err := os.Unsetenv("FASTLY_CUSTOMER_ID"); err != nil {
   304  						t.Fatal(err)
   305  					}
   306  				}()
   307  			}
   308  			var stdout bytes.Buffer
   309  			app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
   310  				opts := testutil.MockGlobalData(testcase.Args, &stdout)
   311  				opts.APIClientFactory = mock.APIClient(testcase.API)
   312  				return opts, nil
   313  			}
   314  			err := app.Run(testcase.Args, nil)
   315  			testutil.AssertErrorContains(t, err, testcase.WantError)
   316  			testutil.AssertStringContains(t, stdout.String(), testcase.WantOutput)
   317  		})
   318  	}
   319  }
   320  
   321  func getToken() (*fastly.Token, error) {
   322  	t := testutil.Date
   323  
   324  	return &fastly.Token{
   325  		TokenID:    fastly.ToPointer("123"),
   326  		Name:       fastly.ToPointer("Foo"),
   327  		UserID:     fastly.ToPointer("456"),
   328  		Services:   []string{"a", "b"},
   329  		Scope:      fastly.ToPointer(fastly.TokenScope(fmt.Sprintf("%s %s", fastly.PurgeAllScope, fastly.GlobalReadScope))),
   330  		IP:         fastly.ToPointer("127.0.0.1"),
   331  		CreatedAt:  &t,
   332  		ExpiresAt:  &t,
   333  		LastUsedAt: &t,
   334  	}, nil
   335  }
   336  
   337  func listTokens(_ *fastly.ListTokensInput) ([]*fastly.Token, error) {
   338  	t := testutil.Date
   339  	token, _ := getToken()
   340  	vs := []*fastly.Token{
   341  		token,
   342  		{
   343  			TokenID:    fastly.ToPointer("456"),
   344  			Name:       fastly.ToPointer("Bar"),
   345  			UserID:     fastly.ToPointer("789"),
   346  			Services:   []string{"a", "b"},
   347  			Scope:      fastly.ToPointer(fastly.GlobalScope),
   348  			IP:         fastly.ToPointer("127.0.0.2"),
   349  			CreatedAt:  &t,
   350  			ExpiresAt:  &t,
   351  			LastUsedAt: &t,
   352  		},
   353  	}
   354  	return vs, nil
   355  }
   356  
   357  func listCustomerTokens(_ *fastly.ListCustomerTokensInput) ([]*fastly.Token, error) {
   358  	return listTokens(nil)
   359  }
   360  
   361  func fileTokensOutput() string {
   362  	return `Deleted tokens
   363  
   364  TOKEN ID
   365  abc
   366  def
   367  xyz`
   368  }
   369  
   370  func describeTokenOutput() string {
   371  	return `
   372  ID: 123
   373  Name: Foo
   374  User ID: 456
   375  Services: a, b
   376  Scope: purge_all global:read
   377  IP: 127.0.0.1
   378  
   379  Created at: 2021-06-15 23:00:00 +0000 UTC
   380  Last used at: 2021-06-15 23:00:00 +0000 UTC
   381  Expires at: 2021-06-15 23:00:00 +0000 UTC`
   382  }
   383  
   384  func listTokenOutputVerbose() string {
   385  	return `Fastly API endpoint: https://api.fastly.com
   386  Fastly API token provided via config file (profile: user)
   387  
   388  
   389  ID: 123
   390  Name: Foo
   391  User ID: 456
   392  Services: a, b
   393  Scope: purge_all global:read
   394  IP: 127.0.0.1
   395  
   396  Created at: 2021-06-15 23:00:00 +0000 UTC
   397  Last used at: 2021-06-15 23:00:00 +0000 UTC
   398  Expires at: 2021-06-15 23:00:00 +0000 UTC
   399  
   400  ID: 456
   401  Name: Bar
   402  User ID: 789
   403  Services: a, b
   404  Scope: global
   405  IP: 127.0.0.2
   406  
   407  Created at: 2021-06-15 23:00:00 +0000 UTC
   408  Last used at: 2021-06-15 23:00:00 +0000 UTC
   409  Expires at: 2021-06-15 23:00:00 +0000 UTC
   410  
   411  `
   412  }
   413  
   414  func listTokenOutputSummary(env bool) string {
   415  	var msg string
   416  	if env {
   417  		msg = "INFO: Listing customer tokens for the FASTLY_CUSTOMER_ID environment variable\n\n"
   418  	}
   419  	return fmt.Sprintf(`%sNAME  TOKEN ID  USER ID  SCOPE                  SERVICES
   420  Foo   123       456      purge_all global:read  a, b
   421  Bar   456       789      global                 a, b`, msg)
   422  }