gitlabhq/gitlab-shell

View on GitHub
internal/command/twofactorverify/twofactorverify_test.go

Summary

Maintainability
A
2 hrs
Test Coverage
package twofactorverify

import (
    "bytes"
    "context"
    "encoding/json"
    "io"
    "net/http"
    "testing"

    "github.com/stretchr/testify/require"

    "gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
    "gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
    "gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
    "gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
    "gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
)

type blockingReader struct{}

func (*blockingReader) Read([]byte) (int, error) {
    waitInfinitely := make(chan struct{})
    <-waitInfinitely

    return 0, nil
}

func setup(t *testing.T) []testserver.TestRequestHandler {
    waitInfinitely := make(chan struct{})
    requests := []testserver.TestRequestHandler{
        {
            Path: "/api/v4/internal/two_factor_manual_otp_check",
            Handler: func(w http.ResponseWriter, r *http.Request) {
                b, err := io.ReadAll(r.Body)
                defer r.Body.Close()

                require.NoError(t, err)

                var requestBody *twofactorverify.RequestBody
                require.NoError(t, json.Unmarshal(b, &requestBody))

                switch requestBody.KeyId {
                case "verify_via_otp", "verify_via_otp_with_push_error":
                    body := map[string]interface{}{
                        "success": true,
                    }
                    json.NewEncoder(w).Encode(body)
                case "wait_infinitely":
                    <-waitInfinitely
                case "error":
                    body := map[string]interface{}{
                        "success": false,
                        "message": "error message",
                    }
                    require.NoError(t, json.NewEncoder(w).Encode(body))
                case "broken":
                    w.WriteHeader(http.StatusInternalServerError)
                }
            },
        },
        {
            Path: "/api/v4/internal/two_factor_push_otp_check",
            Handler: func(w http.ResponseWriter, r *http.Request) {
                b, err := io.ReadAll(r.Body)
                defer r.Body.Close()

                require.NoError(t, err)

                var requestBody *twofactorverify.RequestBody
                require.NoError(t, json.Unmarshal(b, &requestBody))

                switch requestBody.KeyId {
                case "verify_via_push":
                    body := map[string]interface{}{
                        "success": true,
                    }
                    json.NewEncoder(w).Encode(body)
                case "verify_via_otp_with_push_error":
                    w.WriteHeader(http.StatusInternalServerError)
                default:
                    <-waitInfinitely
                }
            },
        },
    }

    return requests
}

const errorHeader = "OTP validation failed: "

func TestExecute(t *testing.T) {
    requests := setup(t)

    url := testserver.StartSocketHttpServer(t, requests)

    testCases := []struct {
        desc           string
        arguments      *commandargs.Shell
        input          io.Reader
        expectedOutput string
    }{
        {
            desc:           "Verify via OTP",
            arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_otp"},
            expectedOutput: "OTP validation successful. Git operations are now allowed.\n",
        },
        {
            desc:           "Verify via OTP",
            arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_otp_with_push_error"},
            expectedOutput: "OTP validation successful. Git operations are now allowed.\n",
        },
        {
            desc:           "Verify via push authentication",
            arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_push"},
            input:          &blockingReader{},
            expectedOutput: "OTP has been validated by Push Authentication. Git operations are now allowed.\n",
        },
        {
            desc:           "With an empty OTP",
            arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_otp"},
            input:          bytes.NewBufferString("\n"),
            expectedOutput: errorHeader + "OTP cannot be blank.\n",
        },
        {
            desc:           "With bad response",
            arguments:      &commandargs.Shell{GitlabKeyId: "-1"},
            expectedOutput: errorHeader + "Parsing failed\n",
        },
        {
            desc:           "With API returns an error",
            arguments:      &commandargs.Shell{GitlabKeyId: "error"},
            expectedOutput: errorHeader + "error message\n",
        },
        {
            desc:           "With API fails",
            arguments:      &commandargs.Shell{GitlabKeyId: "broken"},
            expectedOutput: errorHeader + "Internal API unreachable\n",
        },
        {
            desc:           "With missing arguments",
            arguments:      &commandargs.Shell{},
            expectedOutput: errorHeader + "who='' is invalid\n",
        },
    }

    for _, tc := range testCases {
        t.Run(tc.desc, func(t *testing.T) {
            output := &bytes.Buffer{}

            input := tc.input
            if input == nil {
                input = bytes.NewBufferString("123456\n")
            }

            cmd := &Command{
                Config:     &config.Config{GitlabUrl: url},
                Args:       tc.arguments,
                ReadWriter: &readwriter.ReadWriter{Out: output, In: input},
            }

            _, err := cmd.Execute(context.Background())

            require.NoError(t, err)
            require.Equal(t, prompt+"\n"+tc.expectedOutput, output.String())
        })
    }
}

func TestCanceledContext(t *testing.T) {
    requests := setup(t)

    output := &bytes.Buffer{}

    url := testserver.StartSocketHttpServer(t, requests)
    cmd := &Command{
        Config:     &config.Config{GitlabUrl: url},
        Args:       &commandargs.Shell{GitlabKeyId: "wait_infinitely"},
        ReadWriter: &readwriter.ReadWriter{Out: output, In: &blockingReader{}},
    }

    ctx, cancel := context.WithCancel(context.Background())

    errCh := make(chan error)
    go func() {
        _, err := cmd.Execute(ctx)
        errCh <- err
    }()
    cancel()

    require.NoError(t, <-errCh)
    require.Equal(t, prompt+"\n"+errorHeader+"context canceled\n", output.String())
}