eintopf.info@v0.13.16/service/auth/transport_test.go (about)

     1  // Copyright (C) 2022 The Eintopf authors
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    15  
    16  package auth_test
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"testing"
    25  
    26  	"eintopf.info/internal/xhttptest"
    27  	"eintopf.info/service/auth"
    28  )
    29  
    30  type authService struct{}
    31  
    32  func (a *authService) Login(ctx context.Context, email, password string) (string, error) {
    33  	if email == "admin@example.com" && password == "password" {
    34  		return "valid", nil
    35  	}
    36  	return "", auth.ErrInvalidCredentials
    37  }
    38  
    39  func (a *authService) Authenticate(token string) (string, error) {
    40  	switch token {
    41  	case "valid":
    42  		return "id", nil
    43  	case "errorAuthorization":
    44  		return "error", nil
    45  	case "error":
    46  		return "", fmt.Errorf("error")
    47  	default:
    48  		return "", nil
    49  	}
    50  }
    51  
    52  func (a *authService) Authorize(ctx context.Context, id string) (auth.Role, error) {
    53  	switch id {
    54  	case "id":
    55  		return auth.RoleAdmin, nil
    56  	case "error":
    57  		return auth.RoleNormal, fmt.Errorf("error")
    58  	default:
    59  		return "", nil
    60  	}
    61  }
    62  
    63  func getTestHandler() http.HandlerFunc {
    64  	fn := func(w http.ResponseWriter, req *http.Request) {
    65  		w.WriteHeader(http.StatusOK)
    66  	}
    67  	return http.HandlerFunc(fn)
    68  }
    69  
    70  func mustMarshal(v interface{}) string {
    71  	data, err := json.Marshal(v)
    72  	if err != nil {
    73  		panic("mustMarshal failed")
    74  	}
    75  	return string(data)
    76  }
    77  
    78  func TestRouter(t *testing.T) {
    79  	tests := []xhttptest.HttpTest{
    80  		{
    81  			Name:       "LoginValidCredentials",
    82  			URI:        "/login",
    83  			Method:     "POST",
    84  			Body:       mustMarshal(&auth.Credentials{Email: "admin@example.com", Password: "password"}),
    85  			WantBody:   `{"token":"valid","id":"id","role":"admin"}`,
    86  			WantStatus: 200,
    87  		}, {
    88  			Name:       "LoginInvalidCredentials",
    89  			URI:        "/login",
    90  			Method:     "POST",
    91  			Body:       mustMarshal(&auth.Credentials{Email: "u", Password: "p"}),
    92  			WantBody:   `{"error":"invalid credentials"}`,
    93  			WantStatus: 400,
    94  		}, {
    95  			Name:       "OptionsHandler",
    96  			URI:        "/login",
    97  			Method:     "OPTIONS",
    98  			WantStatus: 200,
    99  		},
   100  	}
   101  
   102  	xhttptest.TestRouter(t, auth.Router(&authService{}), tests)
   103  }
   104  
   105  func TestMiddleware(t *testing.T) {
   106  	tests := []xhttptest.HttpTest{
   107  		{
   108  			Name:       "valid authentication header",
   109  			Method:     "GET",
   110  			Headers:    map[string]string{"Authorization": "valid"},
   111  			WantBody:   "",
   112  			WantStatus: 200,
   113  		}, {
   114  			Name:       "missing authentication header",
   115  			Method:     "GET",
   116  			WantBody:   `{"error":"missing authorization header"}`,
   117  			WantStatus: 401,
   118  		}, {
   119  			Name:       "invalid authentication header",
   120  			Method:     "GET",
   121  			Headers:    map[string]string{"Authorization": "invalid"},
   122  			WantBody:   `{"error":"invalid authentication token"}`,
   123  			WantStatus: 401,
   124  		}, {
   125  			Name:       "error during authentication",
   126  			Method:     "GET",
   127  			Headers:    map[string]string{"Authorization": "error"},
   128  			WantBody:   `{"error":"internal error"}`,
   129  			WantStatus: 500,
   130  		}, {
   131  			Name:       "error during authorization",
   132  			Method:     "GET",
   133  			Headers:    map[string]string{"Authorization": "errorAuthorization"},
   134  			WantBody:   `{"error":"internal error"}`,
   135  			WantStatus: 500,
   136  		},
   137  	}
   138  
   139  	xhttptest.TestMiddleware(t, auth.Middleware(&authService{}), tests)
   140  }
   141  
   142  func testMiddlewareAddsInformationToContext(t *testing.T, middleware func(next http.Handler) http.Handler) {
   143  	testHandlerCalled := false
   144  	getTestHandler := func() http.HandlerFunc {
   145  		fn := func(w http.ResponseWriter, req *http.Request) {
   146  			testHandlerCalled = true
   147  			id, err := auth.UserIDFromContext(req.Context())
   148  			if err != nil && id != "id" {
   149  				t.Fatalf("expected id in context to be 'id'. got %s", id)
   150  			}
   151  			role, err := auth.RoleFromContext(req.Context())
   152  			if err != nil && role != auth.RoleAdmin {
   153  				t.Fatalf("expected role in context to be 'admin'. got %s", role)
   154  			}
   155  		}
   156  		return http.HandlerFunc(fn)
   157  	}
   158  	ts := httptest.NewServer(middleware(getTestHandler()))
   159  	defer ts.Close()
   160  
   161  	client := &http.Client{}
   162  
   163  	req, err := http.NewRequest("GET", ts.URL, nil)
   164  	if err != nil {
   165  		t.Fatalf("expected no error from http.NewRequest. got %s", err)
   166  	}
   167  	req.Header.Set("Authorization", "valid")
   168  	_, err = client.Do(req)
   169  	if err != nil {
   170  		t.Fatalf("expected no error from client.Do. got %s", err)
   171  	}
   172  
   173  	if !testHandlerCalled {
   174  		t.Fatal("expected nextHandler to be called")
   175  	}
   176  }
   177  func TestMiddlewareAddsInformationToContext(t *testing.T) {
   178  	testMiddlewareAddsInformationToContext(t, auth.Middleware(&authService{}))
   179  }
   180  
   181  func TestMiddlewareNoValidationAddsInformationToContext(t *testing.T) {
   182  	testMiddlewareAddsInformationToContext(t, auth.MiddlewareWithOpts(&authService{}, auth.MiddlewareOpts{Validate: false}))
   183  }