-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add integration tests for team and user * add task test-integration * add manual Github workflow for testing integrations * separate tests out to cmd_test package * handle os pipe error in testing * PR feedback * break up tests into smaller chunks * PR feedback
- Loading branch information
1 parent
2b1e239
commit c6b7c15
Showing
7 changed files
with
332 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
name: Integration Tests | ||
|
||
on: | ||
workflow_dispatch: {} | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v4 | ||
- name: Set up Go | ||
uses: actions/setup-go@v5 | ||
with: | ||
go-version-file: go.mod | ||
- name: Install Task | ||
uses: arduino/setup-task@v2 | ||
with: | ||
version: 3.x | ||
repo-token: ${{ secrets.GITHUB_TOKEN }} | ||
- name: Run Tests | ||
run: task test-integration | ||
env: | ||
OPSLEVEL_API_TOKEN: ${{ secrets.OPSLEVEL_PAT_API_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package cmd_test | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/opslevel/cli/cmd" | ||
"github.com/opslevel/opslevel-go/v2024" | ||
) | ||
|
||
const ( | ||
teamFileName = "test_team.yaml" | ||
teamName = "CLI Test Team" | ||
) | ||
|
||
func Test_TeamCRUD(t *testing.T) { | ||
// Create Team | ||
teamToCreate := opslevel.TeamCreateInput{ | ||
Name: teamName, | ||
Responsibilities: opslevel.RefOf("all the things"), | ||
} | ||
teamId, err := createTeam(teamToCreate) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Get Team | ||
createdTeam, err := getTeam(teamId) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if createdTeam.Name != teamToCreate.Name || | ||
createdTeam.Responsibilities != *teamToCreate.Responsibilities { | ||
t.Errorf("Create 'team' failed, expected team '%+v' but got '%+v'", teamToCreate, createdTeam) | ||
} | ||
|
||
// Update Team | ||
teamToUpdate := opslevel.TeamUpdateInput{ | ||
Name: opslevel.RefOf(createdTeam.Name), | ||
Responsibilities: opslevel.RefOf("new things"), | ||
} | ||
updatedTeamId, err := updateTeam(teamId, teamToUpdate) | ||
if err != nil { | ||
_ = deleteTeam(string(createdTeam.Id)) | ||
t.Fatal(err) | ||
} | ||
if string(createdTeam.Id) != updatedTeamId { | ||
t.Errorf("Update 'team' failed, expected returned ID '%s' but got '%s'", string(createdTeam.Id), updatedTeamId) | ||
} | ||
|
||
// Delete Team | ||
if err = deleteTeam(string(createdTeam.Id)); err != nil { | ||
t.Errorf("Delete 'team' failed, got error '%s'", err) | ||
} | ||
} | ||
|
||
func createTeam(teamToCreate opslevel.TeamCreateInput) (string, error) { | ||
if err := writeToYaml(teamFileName, teamToCreate); err != nil { | ||
return "", fmt.Errorf("Error while writing '%v' to file '%s': %v", teamToCreate, teamFileName, err) | ||
} | ||
|
||
cliArgs := []string{teamToCreate.Name, "-f", teamFileName} | ||
cmd.RootCmd.SetArgs(cliArgs) | ||
|
||
// Create Team | ||
createOutput, err := execCmd(Create, "team", cliArgs...) | ||
if err != nil { | ||
return "", fmt.Errorf("Create 'team' failed, got error: %v", err) | ||
} | ||
return asString(createOutput), nil | ||
} | ||
|
||
func getTeam(teamId string) (*opslevel.Team, error) { | ||
getOutput, err := execCmd(Get, "team", teamId) | ||
if err != nil { | ||
return nil, fmt.Errorf("Get 'team' failed, got error: %v", err) | ||
} | ||
|
||
createdTeam, err := jsonToResource[opslevel.Team](getOutput) | ||
if err != nil { | ||
return nil, fmt.Errorf("Failed to convert JSON from API to 'opslevel.Team' struct") | ||
} | ||
return createdTeam, err | ||
} | ||
|
||
func updateTeam(teamId string, teamToUpdate opslevel.TeamUpdateInput) (string, error) { | ||
if err := writeToYaml(teamFileName, teamToUpdate); err != nil { | ||
return "", fmt.Errorf("Error while writing '%v' to file '%s': %v", teamToUpdate, teamFileName, err) | ||
} | ||
|
||
// Store Update Team stuff to "file" | ||
cliArgs := []string{teamId, "-f", teamFileName} | ||
updateOutput, err := execCmd(Update, "team", cliArgs...) | ||
if err != nil { | ||
return "", fmt.Errorf("Update 'team' failed, got error: %v", err) | ||
} | ||
return asString(updateOutput), nil | ||
} | ||
|
||
func deleteTeam(teamId string) error { | ||
_, err := execCmd(Delete, "team", teamId) | ||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,7 +81,7 @@ EOF | |
|
||
var getUserCmd = &cobra.Command{ | ||
Use: "user {ID|EMAIL}", | ||
Short: "Get details about a filter", | ||
Short: "Get details about a user", | ||
Example: `opslevel get user [email protected]`, | ||
Args: cobra.ExactArgs(1), | ||
ArgAliases: []string{"ID"}, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package cmd_test | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/opslevel/cli/cmd" | ||
"github.com/opslevel/opslevel-go/v2024" | ||
) | ||
|
||
const ( | ||
defaultUserRole = opslevel.UserRoleUser | ||
userFileName = "test_user.yaml" | ||
userName = "CLI Test User" | ||
) | ||
|
||
func Test_UserCRUD(t *testing.T) { | ||
expectedUser := opslevel.User{ | ||
UserId: opslevel.UserId{Email: "[email protected]"}, | ||
Name: userName, | ||
} | ||
// Create User | ||
userId, err := createUser(expectedUser) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Get User | ||
createdUser, err := getUser(userId) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if createdUser.Name != expectedUser.Name || | ||
createdUser.Email != expectedUser.Email || | ||
string(createdUser.Role) != string(defaultUserRole) || | ||
!strings.HasPrefix(createdUser.HTMLUrl, "https://app.opslevel.com/users/") { | ||
t.Errorf("Create 'user' failed, expected user '%+v' but got '%+v'", expectedUser, createdUser) | ||
} | ||
|
||
// Update User | ||
expectedUpdatedUser := opslevel.User{ | ||
UserId: createdUser.UserId, | ||
Name: createdUser.Name, | ||
Role: opslevel.UserRoleTeamMember, | ||
} | ||
updatedUserId, err := updateUser(string(createdUser.Id), expectedUpdatedUser) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if string(createdUser.Id) != updatedUserId { | ||
t.Errorf("Update 'user' failed, expected returned ID '%s' but got '%s'", string(createdUser.Id), updatedUserId) | ||
} | ||
|
||
// Delete User | ||
if err = deleteUser(string(createdUser.Id)); err != nil { | ||
t.Error(err) | ||
} | ||
} | ||
|
||
func createUser(expectedUser opslevel.User) (string, error) { | ||
cliArgs := []string{expectedUser.Email, expectedUser.Name} | ||
cmd.RootCmd.SetArgs(cliArgs) | ||
|
||
// Create User | ||
createOutput, err := execCmd(Create, "user", cliArgs...) | ||
if err != nil { | ||
return "", fmt.Errorf("Create 'user' failed, got error: %v", err) | ||
} | ||
return asString(createOutput), nil | ||
} | ||
|
||
func getUser(userId string) (*opslevel.User, error) { | ||
getOutput, err := execCmd(Get, "user", userId) | ||
if err != nil { | ||
return nil, fmt.Errorf("Get 'user' failed, got error: %v", err) | ||
} | ||
|
||
createdUser, err := jsonToResource[opslevel.User](getOutput) | ||
if err != nil { | ||
return nil, fmt.Errorf("Failed to convert JSON from API to 'opslevel.User' struct") | ||
} | ||
return createdUser, err | ||
} | ||
|
||
func updateUser(userId string, userToUpdate opslevel.User) (string, error) { | ||
if err := writeToYaml(userFileName, userToUpdate); err != nil { | ||
return "", fmt.Errorf("Error while writing '%v' to file '%s': %v", userToUpdate, userFileName, err) | ||
} | ||
|
||
// Store Update User stuff to "file" | ||
cliArgs := []string{userId, "-f", userFileName} | ||
updateOutput, err := execCmd(Update, "user", cliArgs...) | ||
if err != nil { | ||
return "", fmt.Errorf("Update 'user' failed, got error: %v", err) | ||
} | ||
return asString(updateOutput), nil | ||
} | ||
|
||
func deleteUser(userId string) error { | ||
_, err := execCmd(Delete, "user", userId) | ||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package cmd | ||
|
||
// Workaround for testing unexported functions. | ||
// | ||
// Running `go help build` displays: | ||
// When compiling packages, build ignores files that end in '_test.go'. | ||
var ( | ||
RootCmd = rootCmd | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package cmd_test | ||
|
||
import ( | ||
"encoding/json" | ||
"io" | ||
"os" | ||
"strings" | ||
|
||
"github.com/opslevel/cli/cmd" | ||
"gopkg.in/yaml.v2" | ||
) | ||
|
||
type Operation string | ||
|
||
const ( | ||
Assign Operation = "assign" | ||
Create Operation = "create" | ||
Delete Operation = "delete" | ||
Example Operation = "example" | ||
Get Operation = "get" | ||
List Operation = "list" | ||
Update Operation = "update" | ||
Unassign Operation = "unassign" | ||
) | ||
|
||
// execute any OpsLevel CLI command | ||
func execCmd(command Operation, resource string, inputs ...string) ([]byte, error) { | ||
cliArgs := []string{string(command), resource} | ||
cliArgs = append(cliArgs, inputs...) | ||
|
||
r, oldStdout, err := redirectStdout() | ||
defer r.Close() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
cmd.RootCmd.SetArgs(cliArgs) | ||
if err = cmd.RootCmd.Execute(); err != nil { | ||
return nil, err | ||
} | ||
|
||
return captureOutput(r, oldStdout) | ||
} | ||
|
||
// redirectStdout redirects os.Stdout to a pipe and returns the read and write ends of the pipe. | ||
func redirectStdout() (*os.File, *os.File, error) { | ||
r, w, err := os.Pipe() | ||
oldStdout := os.Stdout | ||
os.Stdout = w | ||
return r, oldStdout, err | ||
} | ||
|
||
// captureOutput reads from r until EOF and returns the result as a string. | ||
func captureOutput(r *os.File, oldStdout *os.File) ([]byte, error) { | ||
w := os.Stdout | ||
os.Stdout = oldStdout | ||
w.Close() | ||
return io.ReadAll(r) | ||
} | ||
|
||
// convert a simple API response to a string | ||
func asString(data []byte) string { | ||
return strings.TrimSpace(string(data)) | ||
} | ||
|
||
// convert JSON response from API to OpsLevel resource | ||
func jsonToResource[T any](jsonData []byte) (*T, error) { | ||
var resource T | ||
if err := json.Unmarshal(jsonData, &resource); err != nil { | ||
return nil, err | ||
} | ||
return &resource, nil | ||
} | ||
|
||
// write OpsLevel resource to YAML file for commands that read in a file | ||
func writeToYaml(givenFileName string, opslevelResource any) error { | ||
yamlData, err := yaml.Marshal(&opslevelResource) | ||
if err != nil { | ||
return err | ||
} | ||
if err = os.WriteFile(givenFileName, yamlData, 0o644); err != nil { | ||
return err | ||
} | ||
return nil | ||
} |