alby-hub/nip47/controllers/get_budget_controller_test.go
Roland bdce8fe8d2
Some checks are pending
Multiplatform Docker build & push / build (push) Waiting to run
Code quality - linting and typechecking / linting (push) Waiting to run
Backend testing with Postgres / test-postgres (push) Waiting to run
fix: use scope constant in get_budget permission query (#2510)
* fix: use scope constant in get_budget permission query

The get_budget controller filtered the app_permissions scope column with
models.PAY_INVOICE_METHOD, which only matched because the method and
scope constants share the same string value. Use
constants.PAY_INVOICE_SCOPE like every other scope lookup, and document
why the unchecked First result is safe.

Fixes #2503

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: return error from get_budget on unexpected permission query failure

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 12:39:07 +07:00

273 lines
7.9 KiB
Go

package controllers
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/getAlby/go-nostr"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/getAlby/hub/constants"
"github.com/getAlby/hub/db"
"github.com/getAlby/hub/nip47/models"
"github.com/getAlby/hub/tests"
)
const nip47GetBudgetJson = `
{
"method": "get_budget"
}
`
func TestHandleGetBudgetEvent_NoRenewal(t *testing.T) {
ctx := context.TODO()
svc, err := tests.CreateTestService(t)
require.NoError(t, err)
defer svc.Remove()
nip47Request := &models.Request{}
err = json.Unmarshal([]byte(nip47GetBudgetJson), nip47Request)
assert.NoError(t, err)
app, _, err := tests.CreateApp(svc)
assert.NoError(t, err)
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.PAY_INVOICE_SCOPE,
MaxAmountSat: 400,
BudgetRenewal: constants.BUDGET_RENEWAL_NEVER,
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)
dbRequestEvent := &db.RequestEvent{}
err = svc.DB.Create(&dbRequestEvent).Error
assert.NoError(t, err)
var publishedResponse *models.Response
publishResponse := func(response *models.Response, tags nostr.Tags) {
publishedResponse = response
}
NewTestNip47Controller(svc).
HandleGetBudgetEvent(ctx, nip47Request, dbRequestEvent.ID, app, publishResponse)
assert.Equal(t, uint64(400000), publishedResponse.Result.(*getBudgetResponse).TotalBudget)
assert.Equal(t, uint64(0), publishedResponse.Result.(*getBudgetResponse).UsedBudget)
assert.Nil(t, publishedResponse.Result.(*getBudgetResponse).RenewsAt)
assert.Equal(t, constants.BUDGET_RENEWAL_NEVER, publishedResponse.Result.(*getBudgetResponse).RenewalPeriod)
assert.Nil(t, publishedResponse.Error)
}
func TestHandleGetBudgetEvent_NoneUsed(t *testing.T) {
ctx := context.TODO()
svc, err := tests.CreateTestService(t)
require.NoError(t, err)
defer svc.Remove()
nip47Request := &models.Request{}
err = json.Unmarshal([]byte(nip47GetBudgetJson), nip47Request)
assert.NoError(t, err)
app, _, err := tests.CreateApp(svc)
assert.NoError(t, err)
now := time.Now()
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.PAY_INVOICE_SCOPE,
MaxAmountSat: 400,
BudgetRenewal: constants.BUDGET_RENEWAL_MONTHLY,
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)
dbRequestEvent := &db.RequestEvent{}
err = svc.DB.Create(&dbRequestEvent).Error
assert.NoError(t, err)
var publishedResponse *models.Response
publishResponse := func(response *models.Response, tags nostr.Tags) {
publishedResponse = response
}
NewTestNip47Controller(svc).
HandleGetBudgetEvent(ctx, nip47Request, dbRequestEvent.ID, app, publishResponse)
assert.Equal(t, uint64(400000), publishedResponse.Result.(*getBudgetResponse).TotalBudget)
assert.Equal(t, uint64(0), publishedResponse.Result.(*getBudgetResponse).UsedBudget)
renewsAt := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()).AddDate(0, 1, 0).Unix()
assert.Equal(t, uint64(renewsAt), *publishedResponse.Result.(*getBudgetResponse).RenewsAt)
assert.Equal(t, constants.BUDGET_RENEWAL_MONTHLY, publishedResponse.Result.(*getBudgetResponse).RenewalPeriod)
assert.Nil(t, publishedResponse.Error)
}
func TestHandleGetBudgetEvent_HalfUsed(t *testing.T) {
ctx := context.TODO()
svc, err := tests.CreateTestService(t)
require.NoError(t, err)
defer svc.Remove()
nip47Request := &models.Request{}
err = json.Unmarshal([]byte(nip47GetBudgetJson), nip47Request)
assert.NoError(t, err)
app, _, err := tests.CreateApp(svc)
assert.NoError(t, err)
now := time.Now()
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.PAY_INVOICE_SCOPE,
MaxAmountSat: 400,
BudgetRenewal: constants.BUDGET_RENEWAL_MONTHLY,
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)
svc.DB.Create(&db.Transaction{
AppId: &app.ID,
State: constants.TRANSACTION_STATE_SETTLED,
Type: constants.TRANSACTION_TYPE_OUTGOING,
AmountMsat: 200000,
})
dbRequestEvent := &db.RequestEvent{}
err = svc.DB.Create(&dbRequestEvent).Error
assert.NoError(t, err)
var publishedResponse *models.Response
publishResponse := func(response *models.Response, tags nostr.Tags) {
publishedResponse = response
}
NewTestNip47Controller(svc).
HandleGetBudgetEvent(ctx, nip47Request, dbRequestEvent.ID, app, publishResponse)
assert.Equal(t, uint64(400000), publishedResponse.Result.(*getBudgetResponse).TotalBudget)
assert.Equal(t, uint64(200000), publishedResponse.Result.(*getBudgetResponse).UsedBudget)
renewsAt := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()).AddDate(0, 1, 0).Unix()
assert.Equal(t, uint64(renewsAt), *publishedResponse.Result.(*getBudgetResponse).RenewsAt)
assert.Equal(t, constants.BUDGET_RENEWAL_MONTHLY, publishedResponse.Result.(*getBudgetResponse).RenewalPeriod)
assert.Nil(t, publishedResponse.Error)
}
func TestHandleGetBudgetEvent_NoBudget(t *testing.T) {
ctx := context.TODO()
svc, err := tests.CreateTestService(t)
require.NoError(t, err)
defer svc.Remove()
nip47Request := &models.Request{}
err = json.Unmarshal([]byte(nip47GetBudgetJson), nip47Request)
assert.NoError(t, err)
app, _, err := tests.CreateApp(svc)
assert.NoError(t, err)
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.PAY_INVOICE_SCOPE,
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)
svc.DB.Create(&db.Transaction{
AppId: &app.ID,
State: constants.TRANSACTION_STATE_SETTLED,
Type: constants.TRANSACTION_TYPE_OUTGOING,
AmountMsat: 200000,
})
dbRequestEvent := &db.RequestEvent{}
err = svc.DB.Create(&dbRequestEvent).Error
assert.NoError(t, err)
var publishedResponse *models.Response
publishResponse := func(response *models.Response, tags nostr.Tags) {
publishedResponse = response
}
NewTestNip47Controller(svc).
HandleGetBudgetEvent(ctx, nip47Request, dbRequestEvent.ID, app, publishResponse)
assert.Equal(t, struct{}{}, publishedResponse.Result)
assert.Nil(t, publishedResponse.Error)
}
func TestHandleGetBudgetEvent_DatabaseError(t *testing.T) {
ctx := context.TODO()
svc, err := tests.CreateTestService(t)
require.NoError(t, err)
defer svc.Remove()
nip47Request := &models.Request{}
err = json.Unmarshal([]byte(nip47GetBudgetJson), nip47Request)
assert.NoError(t, err)
app, _, err := tests.CreateApp(svc)
assert.NoError(t, err)
dbRequestEvent := &db.RequestEvent{}
err = svc.DB.Create(&dbRequestEvent).Error
assert.NoError(t, err)
// simulate a database failure that is not a record-not-found error
err = svc.DB.Exec("DROP TABLE app_permissions").Error
assert.NoError(t, err)
var publishedResponse *models.Response
publishResponse := func(response *models.Response, tags nostr.Tags) {
publishedResponse = response
}
NewTestNip47Controller(svc).
HandleGetBudgetEvent(ctx, nip47Request, dbRequestEvent.ID, app, publishResponse)
assert.Nil(t, publishedResponse.Result)
require.NotNil(t, publishedResponse.Error)
assert.Equal(t, constants.ERROR_INTERNAL, publishedResponse.Error.Code)
}
func TestHandleGetBudgetEvent_NoPayInvoicePermission(t *testing.T) {
ctx := context.TODO()
svc, err := tests.CreateTestService(t)
require.NoError(t, err)
defer svc.Remove()
nip47Request := &models.Request{}
err = json.Unmarshal([]byte(nip47GetBudgetJson), nip47Request)
assert.NoError(t, err)
app, _, err := tests.CreateApp(svc)
assert.NoError(t, err)
dbRequestEvent := &db.RequestEvent{}
err = svc.DB.Create(&dbRequestEvent).Error
assert.NoError(t, err)
var publishedResponse *models.Response
publishResponse := func(response *models.Response, tags nostr.Tags) {
publishedResponse = response
}
NewTestNip47Controller(svc).
HandleGetBudgetEvent(ctx, nip47Request, dbRequestEvent.ID, app, publishResponse)
assert.Equal(t, struct{}{}, publishedResponse.Result)
assert.Nil(t, publishedResponse.Error)
}