github.com/soulteary/pocket-bookcase@v0.0.0-20240428065142-0b5a9a0fc98a/internal/http/routes/api/v1/auth_test.go (about)

     1  package api_v1
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/sirupsen/logrus"
    13  	"github.com/soulteary/pocket-bookcase/internal/http/middleware"
    14  	"github.com/soulteary/pocket-bookcase/internal/model"
    15  	"github.com/soulteary/pocket-bookcase/internal/testutil"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  func noopLegacyLoginHandler(_ model.Account, _ time.Duration) (string, error) {
    20  	return "", nil
    21  }
    22  
    23  func TestAccountsRoute(t *testing.T) {
    24  	logger := logrus.New()
    25  	ctx := context.TODO()
    26  
    27  	t.Run("login invalid", func(t *testing.T) {
    28  		g := testutil.NewGin()
    29  		_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
    30  		router := NewAuthAPIRoutes(logger, deps, noopLegacyLoginHandler)
    31  		router.Setup(g.Group("/"))
    32  		w := httptest.NewRecorder()
    33  		body := []byte(`{"username": "gopher"}`)
    34  		req := httptest.NewRequest("POST", "/login", bytes.NewBuffer(body))
    35  		g.ServeHTTP(w, req)
    36  
    37  		require.Equal(t, 400, w.Code)
    38  	})
    39  
    40  	t.Run("login incorrect", func(t *testing.T) {
    41  		g := testutil.NewGin()
    42  		_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
    43  		router := NewAuthAPIRoutes(logger, deps, noopLegacyLoginHandler)
    44  		router.Setup(g.Group("/"))
    45  		w := httptest.NewRecorder()
    46  		body := []byte(`{"username": "gopher", "password": "shiori"}`)
    47  		req := httptest.NewRequest("POST", "/login", bytes.NewBuffer(body))
    48  		g.ServeHTTP(w, req)
    49  
    50  		require.Equal(t, 400, w.Code)
    51  	})
    52  
    53  	t.Run("login correct", func(t *testing.T) {
    54  		g := testutil.NewGin()
    55  		_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
    56  		router := NewAuthAPIRoutes(logger, deps, noopLegacyLoginHandler)
    57  		router.Setup(g.Group("/"))
    58  
    59  		// Create an account manually to test
    60  		account := model.Account{
    61  			Username: "shiori",
    62  			Password: "gopher",
    63  			Owner:    true,
    64  		}
    65  		require.NoError(t, deps.Database.SaveAccount(ctx, account))
    66  
    67  		w := httptest.NewRecorder()
    68  		body := []byte(`{"username": "shiori", "password": "gopher"}`)
    69  		req := httptest.NewRequest("POST", "/login", bytes.NewBuffer(body))
    70  		g.ServeHTTP(w, req)
    71  
    72  		require.Equal(t, 200, w.Code)
    73  	})
    74  
    75  	t.Run("check /me (correct token)", func(t *testing.T) {
    76  		_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
    77  
    78  		g := testutil.NewGin()
    79  		g.Use(middleware.AuthMiddleware(deps))
    80  
    81  		router := NewAuthAPIRoutes(logger, deps, noopLegacyLoginHandler)
    82  		router.Setup(g.Group("/"))
    83  
    84  		// Create an account manually to test
    85  		account := model.Account{
    86  			Username: "shiori",
    87  			Password: "gopher",
    88  			Owner:    true,
    89  		}
    90  		require.NoError(t, deps.Database.SaveAccount(ctx, account))
    91  
    92  		token, err := deps.Domains.Auth.CreateTokenForAccount(&account, time.Now().Add(time.Minute))
    93  		require.NoError(t, err)
    94  
    95  		req := httptest.NewRequest("GET", "/me", nil)
    96  		req.Header.Add("Authorization", "Bearer "+token)
    97  		w := httptest.NewRecorder()
    98  		g.ServeHTTP(w, req)
    99  
   100  		require.Equal(t, 200, w.Code)
   101  	})
   102  
   103  	t.Run("check /me (incorrect token)", func(t *testing.T) {
   104  		_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
   105  
   106  		g := testutil.NewGin()
   107  		g.Use(middleware.AuthMiddleware(deps))
   108  
   109  		router := NewAuthAPIRoutes(logger, deps, noopLegacyLoginHandler)
   110  		router.Setup(g.Group("/"))
   111  
   112  		req := httptest.NewRequest("GET", "/me", nil)
   113  		w := httptest.NewRecorder()
   114  		g.ServeHTTP(w, req)
   115  
   116  		require.Equal(t, 403, w.Code)
   117  	})
   118  }
   119  
   120  func TestLoginRequestPayload(t *testing.T) {
   121  	// Test empty payload
   122  	t.Run("test empty payload", func(t *testing.T) {
   123  		payload := loginRequestPayload{}
   124  		err := payload.IsValid()
   125  		require.Error(t, err)
   126  	})
   127  
   128  	// Test empty username
   129  	t.Run("test empty username", func(t *testing.T) {
   130  		payload := loginRequestPayload{
   131  			Password: "gopher",
   132  		}
   133  		err := payload.IsValid()
   134  		require.Error(t, err)
   135  	})
   136  
   137  	// Test empty password
   138  	t.Run("test empty password", func(t *testing.T) {
   139  		payload := loginRequestPayload{
   140  			Username: "shiori",
   141  		}
   142  		err := payload.IsValid()
   143  		require.Error(t, err)
   144  	})
   145  
   146  	// Test valid payload
   147  	t.Run("test valid payload", func(t *testing.T) {
   148  		payload := loginRequestPayload{
   149  			Username: "shiori",
   150  			Password: "gopher",
   151  		}
   152  		err := payload.IsValid()
   153  		require.NoError(t, err)
   154  	})
   155  }
   156  
   157  func TestRefreshHandler(t *testing.T) {
   158  	logger := logrus.New()
   159  	ctx := context.TODO()
   160  	g := testutil.NewGin()
   161  
   162  	_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
   163  	router := NewAuthAPIRoutes(logger, deps, noopLegacyLoginHandler)
   164  	g.Use(middleware.AuthMiddleware(deps)) // Requires AuthMiddleware to manipulate context
   165  	router.Setup(g.Group("/"))
   166  
   167  	t.Run("empty headers", func(t *testing.T) {
   168  		w := testutil.PerformRequest(g, "POST", "/refresh")
   169  		require.Equal(t, http.StatusForbidden, w.Code)
   170  	})
   171  
   172  	t.Run("token invalid", func(t *testing.T) {
   173  		w := testutil.PerformRequest(g, "POST", "/refresh")
   174  		require.Equal(t, http.StatusForbidden, w.Code)
   175  	})
   176  
   177  	t.Run("token valid", func(t *testing.T) {
   178  		token, err := deps.Domains.Auth.CreateTokenForAccount(&model.Account{
   179  			Username: "shiori",
   180  		}, time.Now().Add(time.Minute))
   181  		require.NoError(t, err)
   182  
   183  		w := testutil.PerformRequest(g, "POST", "/refresh", testutil.WithHeader(model.AuthorizationHeader, model.AuthorizationTokenType+" "+token))
   184  
   185  		require.Equal(t, http.StatusAccepted, w.Code)
   186  	})
   187  }
   188  
   189  func TestSettingsHandler(t *testing.T) {
   190  	logger := logrus.New()
   191  	ctx := context.TODO()
   192  	g := testutil.NewGin()
   193  
   194  	_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
   195  	router := NewAuthAPIRoutes(logger, deps, noopLegacyLoginHandler)
   196  	g.Use(middleware.AuthMiddleware(deps))
   197  	router.Setup(g.Group("/"))
   198  
   199  	t.Run("token valid", func(t *testing.T) {
   200  		token, err := deps.Domains.Auth.CreateTokenForAccount(&model.Account{
   201  			Username: "shiori",
   202  		}, time.Now().Add(time.Minute))
   203  		require.NoError(t, err)
   204  
   205  		type settingRequestPayload struct {
   206  			Config model.UserConfig `json:"config"`
   207  		}
   208  		payload := settingRequestPayload{
   209  			Config: model.UserConfig{
   210  				// add your configuration data here
   211  			},
   212  		}
   213  		payloadJSON, err := json.Marshal(payload)
   214  		if err != nil {
   215  			logrus.Printf("problem")
   216  		}
   217  
   218  		w := testutil.PerformRequest(g, "PATCH", "/account", testutil.WithBody(string(payloadJSON)), testutil.WithHeader(model.AuthorizationHeader, model.AuthorizationTokenType+" "+token))
   219  
   220  		require.Equal(t, http.StatusOK, w.Code)
   221  
   222  	})
   223  
   224  	t.Run("config not valid", func(t *testing.T) {
   225  		token, err := deps.Domains.Auth.CreateTokenForAccount(&model.Account{
   226  			Username: "shiori",
   227  		}, time.Now().Add(time.Minute))
   228  		require.NoError(t, err)
   229  
   230  		w := testutil.PerformRequest(g, "PATCH", "/account", testutil.WithBody("notValidConfig"), testutil.WithHeader(model.AuthorizationHeader, model.AuthorizationTokenType+" "+token))
   231  
   232  		require.Equal(t, http.StatusInternalServerError, w.Code)
   233  
   234  	})
   235  	t.Run("Test configure change in database", func(t *testing.T) {
   236  		// Create a tmp database
   237  		g := testutil.NewGin()
   238  		_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
   239  		router := NewAuthAPIRoutes(logger, deps, noopLegacyLoginHandler)
   240  		g.Use(middleware.AuthMiddleware(deps))
   241  		router.Setup(g.Group("/"))
   242  
   243  		// Create an account manually to test
   244  		account := model.Account{
   245  			Username: "shiori",
   246  			Password: "gopher",
   247  			Owner:    true,
   248  			Config: model.UserConfig{
   249  				ShowId:        true,
   250  				ListMode:      true,
   251  				HideThumbnail: true,
   252  				HideExcerpt:   true,
   253  				NightMode:     true,
   254  				KeepMetadata:  true,
   255  				UseArchive:    true,
   256  				CreateEbook:   true,
   257  				MakePublic:    true,
   258  			},
   259  		}
   260  		require.NoError(t, deps.Database.SaveAccount(ctx, account))
   261  
   262  		// Get current user config
   263  		user, _, err := deps.Database.GetAccount(ctx, "shiori")
   264  		require.NoError(t, err)
   265  		require.Equal(t, user.Config, account.Config)
   266  
   267  		// Send Request to update config for user
   268  		token, err := deps.Domains.Auth.CreateTokenForAccount(&user, time.Now().Add(time.Minute))
   269  		require.NoError(t, err)
   270  
   271  		payloadJSON := []byte(`{
   272  			"config": {
   273  			"ShowId": false,
   274  			"ListMode": false,
   275  			"HideThumbnail": false,
   276  			"HideExcerpt": false,
   277  			"NightMode": false,
   278  			"KeepMetadata": false,
   279  			"UseArchive": false,
   280  			"CreateEbook": false,
   281  			"MakePublic": false
   282  			  }
   283  			}`)
   284  
   285  		w := httptest.NewRecorder()
   286  		req := httptest.NewRequest(http.MethodPatch, "/account", bytes.NewBuffer(payloadJSON))
   287  		req.Header.Set("Content-Type", "application/json")
   288  		req.Header.Add("Authorization", "Bearer "+token)
   289  		g.ServeHTTP(w, req)
   290  
   291  		require.Equal(t, 200, w.Code)
   292  		user, _, err = deps.Database.GetAccount(ctx, "shiori")
   293  
   294  		require.NoError(t, err)
   295  		require.NotEqual(t, user.Config, account.Config)
   296  
   297  	})
   298  }