Skip to content

Commit

Permalink
Merge pull request #74 from getAlby/task-tests
Browse files Browse the repository at this point in the history
chore: add tests for handlers
  • Loading branch information
im-adithya authored May 31, 2024
2 parents 8e2c2cb + 29c577f commit 909b11e
Show file tree
Hide file tree
Showing 3 changed files with 301 additions and 13 deletions.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ require (

require (
github.com/DataDog/go-sqllexer v0.0.11 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
golang.org/x/sync v0.7.0 // indirect
Expand Down Expand Up @@ -59,6 +61,7 @@ require (
github.com/philhofer/fwd v1.1.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect
github.com/test-go/testify v1.1.4
github.com/tidwall/gjson v1.17.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
Expand Down
309 changes: 296 additions & 13 deletions internal/nostr/nostr_test.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,294 @@
package nostr

import (
"bytes"
"context"
"encoding/json"
"fmt"
"http-nostr/migrations"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"sync"
"testing"
"time"

"github.com/joho/godotenv"
"github.com/kelseyhightower/envconfig"
"github.com/labstack/echo/v4"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip04"
"github.com/sirupsen/logrus"
"github.com/test-go/testify/assert"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)

// unused
func eventGenerator() (*nostr.Event) {
pubkey := "xxxx"
secret := "xxxx"
const testDB = "postgresql://username@localhost:5432/httpnostr-test"
const ALBY_NWC_PUBKEY = "69effe7b49a6dd5cf525bd0905917a5005ffe480b58eeb8e861418cf3ae760d9"

var testSvc *Service
var privateKey, publicKey string
var webhookResult nostr.Event

func setupTestService() *Service {
ctx := context.Background()

logger := logrus.New()
logger.SetFormatter(&logrus.JSONFormatter{})
logger.SetOutput(os.Stdout)
logger.SetLevel(logrus.InfoLevel)

// Load env file as env variables
godotenv.Load(".env")

cfg := &Config{}
err := envconfig.Process("", cfg)
if err != nil {
logger.Fatalf("Failed to process config: %v", err)
return nil
}

db, err := gorm.Open(postgres.Open(testDB), &gorm.Config{})
if err != nil {
logger.Fatalf("Failed to open DB: %v", err)
return nil
}

err = migrations.Migrate(db)
if err != nil {
logger.Fatalf("Failed to migrate: %v", err)
return nil
}

relay, err := nostr.RelayConnect(ctx, cfg.DefaultRelayURL)
if err != nil {
logger.Fatalf("Failed to connect to default relay: %v", err)
return nil
}

var wg sync.WaitGroup
svc := &Service{
Cfg: cfg,
db: db,
Ctx: ctx,
Wg: &wg,
Logger: logger,
Relay: relay,
subscriptions: make(map[uint]*nostr.Subscription),
}

privateKey = nostr.GeneratePrivateKey()
publicKey, err = nostr.GetPublicKey(privateKey)
if err != nil {
logger.Fatalf("Error converting nostr privkey to pubkey: %v", err)
}

return svc
}

func TestMain(m *testing.M) {
// initialize Service before any tests run
testSvc = setupTestService()
exitCode := m.Run()
os.Exit(exitCode)
}

func TestInfoHandler(t *testing.T) {
if testSvc == nil {
t.Fatal("testService is not initialized")
}

e := echo.New()

tests := []struct {
name string
body map[string]interface{}
expectedCode int
expectedResp interface{}
}{
{
name: "missing_pubkey",
body: map[string]interface{}{},
expectedCode: http.StatusBadRequest,
},
{
name: "valid_request",
body: map[string]interface{}{"walletPubkey": ALBY_NWC_PUBKEY},
expectedCode: http.StatusOK,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
body, _ := json.Marshal(tt.body)
runTest(t, e, http.MethodPost, "/info", bytes.NewBuffer(body), tt.expectedCode, testSvc.InfoHandler)
})
}
}

func TestPublishHandler(t *testing.T) {
if testSvc == nil {
t.Fatal("testService is not initialized")
}

e := echo.New()

tests := []struct {
name string
body map[string]interface{}
expectedCode int
expectedResp interface{}
}{
{
name: "missing_event",
body: map[string]interface{}{},
expectedCode: http.StatusBadRequest,
},
{
name: "valid_request",
body: map[string]interface{}{"event": generateRequestEvent()},
expectedCode: http.StatusOK,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
body, _ := json.Marshal(tt.body)
runTest(t, e, http.MethodPost, "/publish", bytes.NewBuffer(body), tt.expectedCode, testSvc.PublishHandler)
})
}
}

func TestNIP47Handler(t *testing.T) {
if testSvc == nil {
t.Fatal("testService is not initialized")
}

e := echo.New()

tests := []struct {
name string
body map[string]interface{}
expectedCode int
expectedResp interface{}
}{
{
name: "missing_pubkey",
body: map[string]interface{}{},
expectedCode: http.StatusBadRequest,
},
{
name: "missing_event",
body: map[string]interface{}{"walletPubkey": ALBY_NWC_PUBKEY},
expectedCode: http.StatusBadRequest,
},
{
name: "valid_request",
body: map[string]interface{}{
"walletPubkey": ALBY_NWC_PUBKEY,
"event": generateRequestEvent(),
},
expectedCode: http.StatusOK,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
body, _ := json.Marshal(tt.body)
runTest(t, e, http.MethodPost, "/nip47", bytes.NewBuffer(body), tt.expectedCode, testSvc.NIP47Handler)
})
}
}

func TestSubscriptions(t *testing.T) {
if testSvc == nil {
t.Fatal("testService is not initialized")
}

// register the webhook route
e := echo.New()
e.POST("/webhook", webhookHandler)
ts := httptest.NewServer(e)
defer ts.Close()

// subscribe to response events to the pubkey
tags := make(nostr.TagMap)
(tags)["p"] = []string{publicKey}

body, _ := json.Marshal(map[string]interface{}{
"webhookUrl": fmt.Sprintf("%s/webhook", ts.URL),
"filter": nostr.Filter{
Kinds: []int{23195},
Authors: []string{ALBY_NWC_PUBKEY},
Tags: tags,
},
})
req := httptest.NewRequest("POST", "/subscriptions", bytes.NewBuffer(body))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

assert.NoError(t, testSvc.SubscriptionHandler(c))
assert.Equal(t, http.StatusOK, rec.Code)

var subscriptionResp SubscriptionResponse
err := json.Unmarshal(rec.Body.Bytes(), &subscriptionResp)
assert.NoError(t, err)

// make an nip47 request from our pubkey
body, _ = json.Marshal(map[string]interface{}{
"event": generateRequestEvent(),
"walletPubkey": ALBY_NWC_PUBKEY,
})
req = httptest.NewRequest("POST", "/nip47", bytes.NewBuffer(body))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec = httptest.NewRecorder()
c = e.NewContext(req, rec)

assert.NoError(t, testSvc.NIP47Handler(c))
assert.Equal(t, http.StatusOK, rec.Code)

var resp NIP47Response
err = json.Unmarshal(rec.Body.Bytes(), &resp)
assert.NoError(t, err)

// wait for event to be posted on the webhook
time.Sleep(1 * time.Second)

assert.NotNil(t, webhookResult)
// the nip47 response should be the same as webhook response
assert.Equal(t, resp.Event.ID, webhookResult.ID)

req = httptest.NewRequest("DELETE", fmt.Sprintf("/subscriptions/%s", subscriptionResp.SubscriptionId), nil)
rec = httptest.NewRecorder()
c = e.NewContext(req, rec)

// manually set the id param
c.SetParamNames("id")
c.SetParamValues(subscriptionResp.SubscriptionId)

assert.NoError(t, testSvc.StopSubscriptionHandler(c))
assert.Equal(t, http.StatusOK, rec.Code)
}

func runTest(t *testing.T, e *echo.Echo, method string, target string, body io.Reader, expectedCode int, handler echo.HandlerFunc) {
req := httptest.NewRequest(method, target, body)
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

if assert.NoError(t, handler(c)) {
assert.Equal(t, expectedCode, rec.Code)
}
}

func generateRequestEvent() (*nostr.Event) {
var params map[string]interface{}
jsonStr := `{
"method": "pay_invoice",
"params": {
"invoice": "lnbcxxx"
}
"method": "get_info"
}`
decoder := json.NewDecoder(strings.NewReader(jsonStr))
err := decoder.Decode(&params)
Expand All @@ -30,7 +301,7 @@ func eventGenerator() (*nostr.Event) {
return &nostr.Event{}
}

ss, err := nip04.ComputeSharedSecret(pubkey, secret)
ss, err := nip04.ComputeSharedSecret(ALBY_NWC_PUBKEY, privateKey)
if err != nil {
return &nostr.Event{}
}
Expand All @@ -41,17 +312,29 @@ func eventGenerator() (*nostr.Event) {
}

req := &nostr.Event{
PubKey: pubkey,
PubKey: ALBY_NWC_PUBKEY,
CreatedAt: nostr.Now(),
Kind: NIP_47_REQUEST_KIND,
Tags: nostr.Tags{[]string{"p", pubkey}},
Tags: nostr.Tags{[]string{"p", ALBY_NWC_PUBKEY}},
Content: payload,
}

err = req.Sign(secret)
err = req.Sign(privateKey)
if err != nil {
return &nostr.Event{}
}

return req
}
}

func webhookHandler(c echo.Context) error {
var data nostr.Event
if err := c.Bind(&data); err != nil {
return c.JSON(http.StatusBadRequest, ErrorResponse{Error: "invalid payload"})
}

// use mutex here if we use it more than once
webhookResult = data

return c.JSON(http.StatusOK, nil)
}

0 comments on commit 909b11e

Please sign in to comment.