http_service_test.go raw

   1  package http
   2  
   3  import (
   4  	"bytes"
   5  	"encoding/json"
   6  	"io"
   7  	"net/http"
   8  	"net/http/httptest"
   9  	"strconv"
  10  	"testing"
  11  
  12  	"github.com/getAlby/hub/api"
  13  	"github.com/getAlby/hub/config"
  14  	"github.com/getAlby/hub/constants"
  15  	"github.com/getAlby/hub/events"
  16  	"github.com/getAlby/hub/lnclient"
  17  	"github.com/getAlby/hub/logger"
  18  	"github.com/getAlby/hub/tests/db"
  19  	"github.com/getAlby/hub/tests/mocks"
  20  	"github.com/labstack/echo/v4"
  21  	"github.com/sirupsen/logrus"
  22  	"github.com/stretchr/testify/assert"
  23  	"github.com/stretchr/testify/mock"
  24  	"github.com/stretchr/testify/require"
  25  )
  26  
  27  func TestUnlock_IncorrectPassword(t *testing.T) {
  28  	e := echo.New()
  29  	logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
  30  	mockSvc := mocks.NewMockService(t)
  31  	gormDb, err := db.NewDB(t)
  32  	require.NoError(t, err)
  33  	defer db.CloseDB(gormDb)
  34  
  35  	mockEventPublisher := events.NewEventPublisher()
  36  
  37  	mockConfig := mocks.NewMockConfig(t)
  38  	mockConfig.On("GetEnv").Return(&config.AppConfig{})
  39  	mockConfig.On("CheckUnlockPassword", "123").Return(false)
  40  
  41  	mockSvc.On("GetDB").Return(gormDb)
  42  	mockSvc.On("GetConfig").Return(mockConfig)
  43  	mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
  44  	mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
  45  	mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
  46  
  47  	httpSvc := NewHttpService(mockSvc, mockEventPublisher)
  48  	httpSvc.RegisterSharedRoutes(e)
  49  
  50  	requestBody := api.UnlockRequest{UnlockPassword: "123", Permission: "full"}
  51  	jsonBody, _ := json.Marshal(requestBody)
  52  	req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
  53  	req.Header.Set("Content-Type", "application/json") // Set Content-Type header
  54  	rec := httptest.NewRecorder()
  55  	e.ServeHTTP(rec, req)
  56  
  57  	assert.Equal(t, http.StatusUnauthorized, rec.Code)
  58  	mockConfig.AssertNotCalled(t, "GetJWTSecret")
  59  }
  60  
  61  func TestUnlock_UnknownPermission(t *testing.T) {
  62  	e := echo.New()
  63  	logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
  64  	mockSvc := mocks.NewMockService(t)
  65  	gormDb, err := db.NewDB(t)
  66  	require.NoError(t, err)
  67  	defer db.CloseDB(gormDb)
  68  
  69  	mockEventPublisher := events.NewEventPublisher()
  70  
  71  	mockConfig := mocks.NewMockConfig(t)
  72  	mockConfig.On("GetEnv").Return(&config.AppConfig{})
  73  	mockConfig.On("CheckUnlockPassword", "123").Return(true)
  74  
  75  	mockSvc.On("GetDB").Return(gormDb)
  76  	mockSvc.On("GetConfig").Return(mockConfig)
  77  	mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
  78  	mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
  79  	mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
  80  
  81  	httpSvc := NewHttpService(mockSvc, mockEventPublisher)
  82  	httpSvc.RegisterSharedRoutes(e)
  83  
  84  	requestBody := api.UnlockRequest{UnlockPassword: "123", Permission: "unknown"}
  85  	jsonBody, _ := json.Marshal(requestBody)
  86  	req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
  87  	req.Header.Set("Content-Type", "application/json") // Set Content-Type header
  88  	rec := httptest.NewRecorder()
  89  	e.ServeHTTP(rec, req)
  90  
  91  	assert.Equal(t, http.StatusBadRequest, rec.Code)
  92  	mockConfig.AssertNotCalled(t, "GetJWTSecret")
  93  }
  94  
  95  // TestUnlock_RateLimited verifies that repeated requests to an unlock-password
  96  // endpoint are throttled with HTTP 429 once the limit is exceeded.
  97  func TestUnlock_RateLimited(t *testing.T) {
  98  	e := echo.New()
  99  	logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
 100  	mockSvc := mocks.NewMockService(t)
 101  	gormDb, err := db.NewDB(t)
 102  	require.NoError(t, err)
 103  	defer db.CloseDB(gormDb)
 104  
 105  	mockEventPublisher := events.NewEventPublisher()
 106  
 107  	mockConfig := mocks.NewMockConfig(t)
 108  	mockConfig.On("GetEnv").Return(&config.AppConfig{})
 109  	mockConfig.On("CheckUnlockPassword", "wrong").Return(false)
 110  
 111  	mockSvc.On("GetDB").Return(gormDb)
 112  	mockSvc.On("GetConfig").Return(mockConfig)
 113  	mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
 114  	mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
 115  	mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
 116  
 117  	httpSvc := NewHttpService(mockSvc, mockEventPublisher)
 118  	httpSvc.RegisterSharedRoutes(e)
 119  
 120  	jsonBody, _ := json.Marshal(api.UnlockRequest{UnlockPassword: "wrong", Permission: "full"})
 121  	send := func() int {
 122  		req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
 123  		req.Header.Set("Content-Type", "application/json")
 124  		rec := httptest.NewRecorder()
 125  		e.ServeHTTP(rec, req)
 126  		return rec.Code
 127  	}
 128  
 129  	// the burst of 2 is served (wrong password, so unauthorized)
 130  	assert.Equal(t, http.StatusUnauthorized, send())
 131  	assert.Equal(t, http.StatusUnauthorized, send())
 132  	// the next request exceeds the limit and is rejected with 429
 133  	assert.Equal(t, http.StatusTooManyRequests, send())
 134  }
 135  
 136  // TestUnlock_RateLimitNotBypassedBySpoofedIP verifies that the unlock rate
 137  // limiter is global rather than per-IP: varying the X-Forwarded-For header per
 138  // request does not grant each request a fresh bucket.
 139  func TestUnlock_RateLimitNotBypassedBySpoofedIP(t *testing.T) {
 140  	e := echo.New()
 141  	logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
 142  	mockSvc := mocks.NewMockService(t)
 143  	gormDb, err := db.NewDB(t)
 144  	require.NoError(t, err)
 145  	defer db.CloseDB(gormDb)
 146  
 147  	mockEventPublisher := events.NewEventPublisher()
 148  
 149  	mockConfig := mocks.NewMockConfig(t)
 150  	mockConfig.On("GetEnv").Return(&config.AppConfig{})
 151  	mockConfig.On("CheckUnlockPassword", "wrong").Return(false)
 152  
 153  	mockSvc.On("GetDB").Return(gormDb)
 154  	mockSvc.On("GetConfig").Return(mockConfig)
 155  	mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
 156  	mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
 157  	mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
 158  
 159  	httpSvc := NewHttpService(mockSvc, mockEventPublisher)
 160  	httpSvc.RegisterSharedRoutes(e)
 161  
 162  	jsonBody, _ := json.Marshal(api.UnlockRequest{UnlockPassword: "wrong", Permission: "full"})
 163  
 164  	send := func(forwardedFor string) int {
 165  		req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
 166  		req.Header.Set("Content-Type", "application/json")
 167  		req.Header.Set("X-Forwarded-For", forwardedFor)
 168  		rec := httptest.NewRecorder()
 169  		e.ServeHTTP(rec, req)
 170  		return rec.Code
 171  	}
 172  
 173  	rateLimited := 0
 174  	for i := 0; i < 12; i++ {
 175  		// each request presents a distinct client address
 176  		if send("10.0.0."+strconv.Itoa(i)) == http.StatusTooManyRequests {
 177  			rateLimited++
 178  		}
 179  	}
 180  
 181  	assert.Positive(t, rateLimited, "spoofing X-Forwarded-For must not grant a fresh rate-limit bucket")
 182  }
 183  
 184  func TestGetApps_NoToken(t *testing.T) {
 185  	e := echo.New()
 186  	logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
 187  	mockSvc := mocks.NewMockService(t)
 188  	gormDb, err := db.NewDB(t)
 189  	require.NoError(t, err)
 190  	defer db.CloseDB(gormDb)
 191  
 192  	mockEventPublisher := mocks.NewMockEventPublisher(t)
 193  
 194  	mockConfig := mocks.NewMockConfig(t)
 195  	mockConfig.On("GetEnv").Return(&config.AppConfig{})
 196  
 197  	mockSvc.On("GetDB").Return(gormDb)
 198  	mockSvc.On("GetConfig").Return(mockConfig)
 199  	mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
 200  	mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
 201  	mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
 202  
 203  	httpSvc := NewHttpService(mockSvc, mockEventPublisher)
 204  	httpSvc.RegisterSharedRoutes(e)
 205  
 206  	req := httptest.NewRequest(http.MethodGet, "/api/apps", nil)
 207  	rec := httptest.NewRecorder()
 208  	e.ServeHTTP(rec, req)
 209  
 210  	assert.Equal(t, http.StatusUnauthorized, rec.Code)
 211  }
 212  
 213  func TestUnlock_NodeNotStarted(t *testing.T) {
 214  	e := echo.New()
 215  	logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
 216  	mockSvc := mocks.NewMockService(t)
 217  	gormDb, err := db.NewDB(t)
 218  	require.NoError(t, err)
 219  	defer db.CloseDB(gormDb)
 220  
 221  	mockEventPublisher := events.NewEventPublisher()
 222  
 223  	mockConfig := mocks.NewMockConfig(t)
 224  	mockConfig.On("GetEnv").Return(&config.AppConfig{})
 225  	mockConfig.On("CheckUnlockPassword", "123").Return(true)
 226  
 227  	mockSvc.On("GetDB").Return(gormDb)
 228  	mockSvc.On("GetConfig").Return(mockConfig)
 229  	mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
 230  	mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
 231  	mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
 232  	mockSvc.On("GetLNClient").Return(nil)
 233  
 234  	httpSvc := NewHttpService(mockSvc, mockEventPublisher)
 235  	httpSvc.RegisterSharedRoutes(e)
 236  
 237  	requestBody := api.UnlockRequest{UnlockPassword: "123", Permission: "full"}
 238  	jsonBody, _ := json.Marshal(requestBody)
 239  	req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
 240  	req.Header.Set("Content-Type", "application/json")
 241  	rec := httptest.NewRecorder()
 242  	e.ServeHTTP(rec, req)
 243  
 244  	assert.Equal(t, http.StatusBadRequest, rec.Code)
 245  
 246  	body, err := io.ReadAll(rec.Body)
 247  	require.NoError(t, err)
 248  
 249  	var response ErrorResponse
 250  	err = json.Unmarshal(body, &response)
 251  	require.NoError(t, err)
 252  	assert.Equal(t, "Node is not running, start it before unlocking.", response.Message)
 253  	mockConfig.AssertNotCalled(t, "GetJWTSecret")
 254  }
 255  
 256  func TestGetApps_ReadonlyPermission(t *testing.T) {
 257  	e := echo.New()
 258  	logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
 259  	mockSvc := mocks.NewMockService(t)
 260  	gormDb, err := db.NewDB(t)
 261  	require.NoError(t, err)
 262  	defer db.CloseDB(gormDb)
 263  
 264  	mockEventPublisher := events.NewEventPublisher()
 265  
 266  	mockConfig := mocks.NewMockConfig(t)
 267  	mockConfig.On("GetEnv").Return(&config.AppConfig{})
 268  	mockConfig.On("CheckUnlockPassword", "123").Return(true)
 269  	mockConfig.On("GetJWTSecret").Return("dummy secret", nil)
 270  
 271  	mockSvc.On("GetDB").Return(gormDb)
 272  	mockSvc.On("GetConfig").Return(mockConfig)
 273  	mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
 274  	mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
 275  	mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
 276  	lnClient := mocks.NewMockLNClient(t)
 277  	lnClient.On("GetNodeStatus", mock.Anything).Return(&lnclient.NodeStatus{}, nil)
 278  	mockSvc.On("GetLNClient").Return(lnClient)
 279  
 280  	httpSvc := NewHttpService(mockSvc, mockEventPublisher)
 281  	httpSvc.RegisterSharedRoutes(e)
 282  
 283  	requestBody := api.UnlockRequest{UnlockPassword: "123", Permission: "readonly"}
 284  	jsonBody, _ := json.Marshal(requestBody)
 285  	req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
 286  	req.Header.Set("Content-Type", "application/json") // Set Content-Type header
 287  	rec := httptest.NewRecorder()
 288  	e.ServeHTTP(rec, req)
 289  
 290  	assert.Equal(t, http.StatusOK, rec.Code)
 291  
 292  	body, err := io.ReadAll(rec.Body)
 293  	require.NoError(t, err)
 294  
 295  	type authTokenResponse struct {
 296  		Token string `json:"token"`
 297  	}
 298  
 299  	var unlockAuthTokenResponse authTokenResponse
 300  	err = json.Unmarshal(body, &unlockAuthTokenResponse)
 301  	require.NoError(t, err)
 302  	assert.NotEmpty(t, unlockAuthTokenResponse.Token)
 303  
 304  	req2 := httptest.NewRequest(http.MethodGet, "/api/apps", nil)
 305  	req2.Header.Set("Authorization", "Bearer "+unlockAuthTokenResponse.Token)
 306  	rec2 := httptest.NewRecorder()
 307  	e.ServeHTTP(rec2, req2)
 308  
 309  	assert.Equal(t, http.StatusOK, rec2.Code)
 310  }
 311  
 312  func TestGetApps_FullPermission(t *testing.T) {
 313  	e := echo.New()
 314  	logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
 315  	mockSvc := mocks.NewMockService(t)
 316  	gormDb, err := db.NewDB(t)
 317  	require.NoError(t, err)
 318  	defer db.CloseDB(gormDb)
 319  
 320  	mockEventPublisher := events.NewEventPublisher()
 321  
 322  	mockConfig := mocks.NewMockConfig(t)
 323  	mockConfig.On("GetEnv").Return(&config.AppConfig{})
 324  	mockConfig.On("CheckUnlockPassword", "123").Return(true)
 325  	mockConfig.On("GetJWTSecret").Return("dummy secret", nil)
 326  
 327  	mockSvc.On("GetDB").Return(gormDb)
 328  	mockSvc.On("GetConfig").Return(mockConfig)
 329  	mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
 330  	mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
 331  	mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
 332  	lnClient := mocks.NewMockLNClient(t)
 333  	lnClient.On("GetNodeStatus", mock.Anything).Return(&lnclient.NodeStatus{}, nil)
 334  	mockSvc.On("GetLNClient").Return(lnClient)
 335  
 336  	httpSvc := NewHttpService(mockSvc, mockEventPublisher)
 337  	httpSvc.RegisterSharedRoutes(e)
 338  
 339  	requestBody := api.UnlockRequest{UnlockPassword: "123", Permission: "full"}
 340  	jsonBody, _ := json.Marshal(requestBody)
 341  	req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
 342  	req.Header.Set("Content-Type", "application/json") // Set Content-Type header
 343  	rec := httptest.NewRecorder()
 344  	e.ServeHTTP(rec, req)
 345  
 346  	assert.Equal(t, http.StatusOK, rec.Code)
 347  
 348  	body, err := io.ReadAll(rec.Body)
 349  	require.NoError(t, err)
 350  
 351  	type authTokenResponse struct {
 352  		Token string `json:"token"`
 353  	}
 354  
 355  	var unlockAuthTokenResponse authTokenResponse
 356  	err = json.Unmarshal(body, &unlockAuthTokenResponse)
 357  	require.NoError(t, err)
 358  	assert.NotEmpty(t, unlockAuthTokenResponse.Token)
 359  
 360  	req2 := httptest.NewRequest(http.MethodGet, "/api/apps", nil)
 361  	req2.Header.Set("Authorization", "Bearer "+unlockAuthTokenResponse.Token)
 362  	rec2 := httptest.NewRecorder()
 363  	e.ServeHTTP(rec2, req2)
 364  
 365  	assert.Equal(t, http.StatusOK, rec2.Code)
 366  }
 367  
 368  func TestCreateApp_NoToken(t *testing.T) {
 369  	e := echo.New()
 370  	logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
 371  	mockSvc := mocks.NewMockService(t)
 372  	gormDb, err := db.NewDB(t)
 373  	require.NoError(t, err)
 374  	defer db.CloseDB(gormDb)
 375  
 376  	mockEventPublisher := mocks.NewMockEventPublisher(t)
 377  
 378  	mockConfig := mocks.NewMockConfig(t)
 379  	mockConfig.On("GetEnv").Return(&config.AppConfig{})
 380  
 381  	mockSvc.On("GetDB").Return(gormDb)
 382  	mockSvc.On("GetConfig").Return(mockConfig)
 383  	mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
 384  	mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
 385  	mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
 386  
 387  	httpSvc := NewHttpService(mockSvc, mockEventPublisher)
 388  	httpSvc.RegisterSharedRoutes(e)
 389  
 390  	requestBody := api.CreateAppRequest{Name: "Test app", Scopes: []string{constants.PAY_INVOICE_SCOPE}}
 391  	jsonBody, _ := json.Marshal(requestBody)
 392  	req := httptest.NewRequest(http.MethodPost, "/api/apps", bytes.NewBuffer(jsonBody))
 393  	req.Header.Set("Content-Type", "application/json") // Set Content-Type header
 394  	rec := httptest.NewRecorder()
 395  	e.ServeHTTP(rec, req)
 396  
 397  	assert.Equal(t, http.StatusUnauthorized, rec.Code)
 398  }
 399  
 400  func TestCreateApp_FullPermission(t *testing.T) {
 401  	e := echo.New()
 402  	logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
 403  	mockSvc := mocks.NewMockService(t)
 404  	gormDb, err := db.NewDB(t)
 405  	require.NoError(t, err)
 406  	defer db.CloseDB(gormDb)
 407  
 408  	mockEventPublisher := events.NewEventPublisher()
 409  
 410  	mockConfig := mocks.NewMockConfig(t)
 411  	mockConfig.On("GetEnv").Return(&config.AppConfig{})
 412  	mockConfig.On("CheckUnlockPassword", "123").Return(true)
 413  	mockConfig.On("GetJWTSecret").Return("dummy secret", nil)
 414  	mockConfig.On("GetRelayUrls").Return([]string{})
 415  
 416  	mockKeys := mocks.NewMockKeys(t)
 417  	mockKeys.On("GetAppWalletKey", uint(1)).Return("", nil)
 418  
 419  	mockAlbyOAuthService := mocks.NewMockAlbyOAuthService(t)
 420  	mockAlbyOAuthService.On("GetLightningAddress").Return("", nil)
 421  
 422  	mockSvc.On("GetDB").Return(gormDb)
 423  	mockSvc.On("GetConfig").Return(mockConfig)
 424  	mockSvc.On("GetKeys").Return(mockKeys)
 425  	mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
 426  	mockSvc.On("GetAlbyOAuthSvc").Return(mockAlbyOAuthService)
 427  	lnClient := mocks.NewMockLNClient(t)
 428  	lnClient.On("GetNodeStatus", mock.Anything).Return(&lnclient.NodeStatus{}, nil)
 429  	mockSvc.On("GetLNClient").Return(lnClient)
 430  
 431  	httpSvc := NewHttpService(mockSvc, mockEventPublisher)
 432  	httpSvc.RegisterSharedRoutes(e)
 433  
 434  	requestBody := api.UnlockRequest{UnlockPassword: "123", Permission: "full"}
 435  	jsonBody, _ := json.Marshal(requestBody)
 436  	req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
 437  	req.Header.Set("Content-Type", "application/json") // Set Content-Type header
 438  	rec := httptest.NewRecorder()
 439  	e.ServeHTTP(rec, req)
 440  
 441  	assert.Equal(t, http.StatusOK, rec.Code)
 442  
 443  	body, err := io.ReadAll(rec.Body)
 444  	require.NoError(t, err)
 445  
 446  	type authTokenResponse struct {
 447  		Token string `json:"token"`
 448  	}
 449  
 450  	var unlockAuthTokenResponse authTokenResponse
 451  	err = json.Unmarshal(body, &unlockAuthTokenResponse)
 452  	require.NoError(t, err)
 453  	assert.NotEmpty(t, unlockAuthTokenResponse.Token)
 454  
 455  	requestBody2 := api.CreateAppRequest{Name: "Test app", Scopes: []string{constants.PAY_INVOICE_SCOPE}}
 456  	jsonBody2, _ := json.Marshal(requestBody2)
 457  	req2 := httptest.NewRequest(http.MethodPost, "/api/apps", bytes.NewBuffer(jsonBody2))
 458  	req2.Header.Set("Authorization", "Bearer "+unlockAuthTokenResponse.Token)
 459  	req2.Header.Set("Content-Type", "application/json") // Set Content-Type header
 460  
 461  	rec2 := httptest.NewRecorder()
 462  	e.ServeHTTP(rec2, req2)
 463  
 464  	assert.Equal(t, http.StatusOK, rec2.Code)
 465  }
 466  
 467  func TestCreateApp_ReadonlyPermission(t *testing.T) {
 468  	e := echo.New()
 469  	logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
 470  	mockSvc := mocks.NewMockService(t)
 471  	gormDb, err := db.NewDB(t)
 472  	require.NoError(t, err)
 473  	defer db.CloseDB(gormDb)
 474  
 475  	mockEventPublisher := events.NewEventPublisher()
 476  
 477  	mockConfig := mocks.NewMockConfig(t)
 478  	mockConfig.On("GetEnv").Return(&config.AppConfig{})
 479  	mockConfig.On("CheckUnlockPassword", "123").Return(true)
 480  	mockConfig.On("GetJWTSecret").Return("dummy secret", nil)
 481  
 482  	mockKeys := mocks.NewMockKeys(t)
 483  
 484  	mockAlbyOAuthService := mocks.NewMockAlbyOAuthService(t)
 485  
 486  	mockSvc.On("GetDB").Return(gormDb)
 487  	mockSvc.On("GetConfig").Return(mockConfig)
 488  	mockSvc.On("GetKeys").Return(mockKeys)
 489  	mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
 490  	mockSvc.On("GetAlbyOAuthSvc").Return(mockAlbyOAuthService)
 491  	lnClient := mocks.NewMockLNClient(t)
 492  	lnClient.On("GetNodeStatus", mock.Anything).Return(&lnclient.NodeStatus{}, nil)
 493  	mockSvc.On("GetLNClient").Return(lnClient)
 494  
 495  	httpSvc := NewHttpService(mockSvc, mockEventPublisher)
 496  	httpSvc.RegisterSharedRoutes(e)
 497  
 498  	requestBody := api.UnlockRequest{UnlockPassword: "123", Permission: "readonly"}
 499  	jsonBody, _ := json.Marshal(requestBody)
 500  	req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
 501  	req.Header.Set("Content-Type", "application/json") // Set Content-Type header
 502  	rec := httptest.NewRecorder()
 503  	e.ServeHTTP(rec, req)
 504  
 505  	assert.Equal(t, http.StatusOK, rec.Code)
 506  
 507  	body, err := io.ReadAll(rec.Body)
 508  	require.NoError(t, err)
 509  
 510  	type authTokenResponse struct {
 511  		Token string `json:"token"`
 512  	}
 513  
 514  	var unlockAuthTokenResponse authTokenResponse
 515  	err = json.Unmarshal(body, &unlockAuthTokenResponse)
 516  	require.NoError(t, err)
 517  	assert.NotEmpty(t, unlockAuthTokenResponse.Token)
 518  
 519  	requestBody2 := api.CreateAppRequest{Name: "Test app", Scopes: []string{constants.PAY_INVOICE_SCOPE}}
 520  	jsonBody2, _ := json.Marshal(requestBody2)
 521  	req2 := httptest.NewRequest(http.MethodPost, "/api/apps", bytes.NewBuffer(jsonBody2))
 522  	req2.Header.Set("Authorization", "Bearer "+unlockAuthTokenResponse.Token)
 523  	req2.Header.Set("Content-Type", "application/json") // Set Content-Type header
 524  
 525  	rec2 := httptest.NewRecorder()
 526  	e.ServeHTTP(rec2, req2)
 527  
 528  	assert.Equal(t, http.StatusForbidden, rec2.Code)
 529  }
 530  
 531  func TestGetLogOutput_ReadonlyPermission(t *testing.T) {
 532  	e := echo.New()
 533  	logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
 534  	mockSvc := mocks.NewMockService(t)
 535  	gormDb, err := db.NewDB(t)
 536  	require.NoError(t, err)
 537  	defer db.CloseDB(gormDb)
 538  
 539  	mockEventPublisher := events.NewEventPublisher()
 540  
 541  	mockConfig := mocks.NewMockConfig(t)
 542  	mockConfig.On("GetEnv").Return(&config.AppConfig{})
 543  	mockConfig.On("CheckUnlockPassword", "123").Return(true)
 544  	mockConfig.On("GetJWTSecret").Return("dummy secret", nil)
 545  
 546  	mockSvc.On("GetDB").Return(gormDb)
 547  	mockSvc.On("GetConfig").Return(mockConfig)
 548  	mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
 549  	mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
 550  	mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
 551  	lnClient := mocks.NewMockLNClient(t)
 552  	lnClient.On("GetNodeStatus", mock.Anything).Return(&lnclient.NodeStatus{}, nil)
 553  	mockSvc.On("GetLNClient").Return(lnClient)
 554  
 555  	httpSvc := NewHttpService(mockSvc, mockEventPublisher)
 556  	httpSvc.RegisterSharedRoutes(e)
 557  
 558  	requestBody := api.UnlockRequest{UnlockPassword: "123", Permission: "readonly"}
 559  	jsonBody, _ := json.Marshal(requestBody)
 560  	req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
 561  	req.Header.Set("Content-Type", "application/json") // Set Content-Type header
 562  	rec := httptest.NewRecorder()
 563  	e.ServeHTTP(rec, req)
 564  
 565  	assert.Equal(t, http.StatusOK, rec.Code)
 566  
 567  	body, err := io.ReadAll(rec.Body)
 568  	require.NoError(t, err)
 569  
 570  	type authTokenResponse struct {
 571  		Token string `json:"token"`
 572  	}
 573  
 574  	var unlockAuthTokenResponse authTokenResponse
 575  	err = json.Unmarshal(body, &unlockAuthTokenResponse)
 576  	require.NoError(t, err)
 577  	assert.NotEmpty(t, unlockAuthTokenResponse.Token)
 578  
 579  	req2 := httptest.NewRequest(http.MethodGet, "/api/log/app", nil)
 580  	req2.Header.Set("Authorization", "Bearer "+unlockAuthTokenResponse.Token)
 581  	rec2 := httptest.NewRecorder()
 582  	e.ServeHTTP(rec2, req2)
 583  
 584  	assert.Equal(t, http.StatusForbidden, rec2.Code)
 585  }
 586  
 587  func TestGetLogOutput_FullPermission(t *testing.T) {
 588  	e := echo.New()
 589  	logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
 590  	mockSvc := mocks.NewMockService(t)
 591  	gormDb, err := db.NewDB(t)
 592  	require.NoError(t, err)
 593  	defer db.CloseDB(gormDb)
 594  
 595  	mockEventPublisher := events.NewEventPublisher()
 596  
 597  	mockConfig := mocks.NewMockConfig(t)
 598  	mockConfig.On("GetEnv").Return(&config.AppConfig{})
 599  	mockConfig.On("CheckUnlockPassword", "123").Return(true)
 600  	mockConfig.On("GetJWTSecret").Return("dummy secret", nil)
 601  
 602  	mockSvc.On("GetDB").Return(gormDb)
 603  	mockSvc.On("GetConfig").Return(mockConfig)
 604  	mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
 605  	mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
 606  	mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
 607  	lnClient := mocks.NewMockLNClient(t)
 608  	lnClient.On("GetNodeStatus", mock.Anything).Return(&lnclient.NodeStatus{}, nil)
 609  	mockSvc.On("GetLNClient").Return(lnClient)
 610  
 611  	httpSvc := NewHttpService(mockSvc, mockEventPublisher)
 612  	httpSvc.RegisterSharedRoutes(e)
 613  
 614  	requestBody := api.UnlockRequest{UnlockPassword: "123", Permission: "full"}
 615  	jsonBody, _ := json.Marshal(requestBody)
 616  	req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
 617  	req.Header.Set("Content-Type", "application/json") // Set Content-Type header
 618  	rec := httptest.NewRecorder()
 619  	e.ServeHTTP(rec, req)
 620  
 621  	assert.Equal(t, http.StatusOK, rec.Code)
 622  
 623  	body, err := io.ReadAll(rec.Body)
 624  	require.NoError(t, err)
 625  
 626  	type authTokenResponse struct {
 627  		Token string `json:"token"`
 628  	}
 629  
 630  	var unlockAuthTokenResponse authTokenResponse
 631  	err = json.Unmarshal(body, &unlockAuthTokenResponse)
 632  	require.NoError(t, err)
 633  	assert.NotEmpty(t, unlockAuthTokenResponse.Token)
 634  
 635  	req2 := httptest.NewRequest(http.MethodGet, "/api/log/app", nil)
 636  	req2.Header.Set("Authorization", "Bearer "+unlockAuthTokenResponse.Token)
 637  	rec2 := httptest.NewRecorder()
 638  	e.ServeHTTP(rec2, req2)
 639  
 640  	assert.Equal(t, http.StatusOK, rec2.Code)
 641  
 642  	var logResponse api.GetLogOutputResponse
 643  	err = json.Unmarshal(rec2.Body.Bytes(), &logResponse)
 644  	require.NoError(t, err)
 645  }
 646