So I currently have a function that will take in a string APIKey to check it against my Mongo collection. If nothing is found (not authenticated), it returns false - if a user is found, it returns true. My problem, however, is I'm unsure how to integrate this with a Martini POST route. Here is my code:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/go-martini/martini"
_ "github.com/joho/godotenv/autoload"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type User struct {
Name string
APIKey string
}
func validateAPIKey(users *mongo.Collection, APIKey string) bool {
var user User
filter := bson.D{{"APIKey", APIKey}}
if err := users.FindOne(context.TODO(), filter).Decode(&user); err != nil {
fmt.Printf("Found 0 results for API Key: %s\n", APIKey)
return false
}
fmt.Printf("Found: %s\n", user.Name)
return true
}
func uploadHandler() {
}
func main() {
mongoURI := os.Getenv("MONGO_URI")
mongoOptions := options.Client().ApplyURI(mongoURI)
client, _ := mongo.Connect(context.TODO(), mongoOptions)
defer client.Disconnect(context.TODO())
if err := client.Ping(context.TODO(), nil); err != nil {
log.Fatal(err, "Unable to access MongoDB server, exiting...")
}
// users := client.Database("sharex_api").Collection("authorized_users") // commented out when testing to ignore unused warnings
m := martini.Classic()
m.Post("/api/v1/upload", uploadHandler)
m.RunOnAddr(":8085")
}
The validateAPIKey function works exactly as intended if tested alone, I am just unsure how I would run this function for a specific endpoint (in this case, /api/v1/upload).
Related
I am using Go 1.19 on a windows machine with 8 cores, operating system is Windows 10 Pro.
I used the mockgen tool to generate the mock. When I debug my test I see the mocked method is recorded when I execute the EXPECT() function.
The mocked function is called, but the test fails with 'missing call' on the mocked function.
I cannot see what I am doing wrong, can anyone please point it out ?
Directory Structure :
cmd
configure.go
configure_test.go
mocks
mock_validator.go
validator
validator.go
user
user.go
go.mod
main.go
* Contents of main.go
package main
import (
"localdev/mockexample/cmd"
)
func main() {
cmd.Configure()
}
* Contents of configure.go
package cmd
import (
"fmt"
"localdev/mockexample/user"
"os"
"localdev/mockexample/validator"
)
var (
name, password string
)
func Configure() {
name := os.Args[1]
password := os.Args[2]
user, err := validate(validator.NewValidator(name, password))
if err != nil {
fmt.Printf("%v\n", err)
return
}
fmt.Printf("Credentials are valid. Welcome: %s %s\n", user.FirstName, user.LastName)
}
func validate(validator validator.Validator) (*user.Data, error) {
user, err := validator.ValidateUser()
if err != nil {
return nil, fmt.Errorf("some thing went wrong. %v", err)
}
return user, nil
}
* Contents of validator.go
package validator
import (
"fmt"
"localdev/mockexample/user"
)
//go:generate mockgen -destination=../mocks/mock_validator.go -package=mocks localdev/mockexample/validator Validator
type Validator interface {
ValidateUser() (*user.Data, error)
}
type ValidationRequest struct {
Command string
Name string
Password string
}
func (vr ValidationRequest) ValidateUser() (*user.Data, error) {
if vr.Name == "bob" && vr.Password == "1234" {
return &user.Data{UserID: "123", UserName: "bsmith", FirstName: "Bob", LastName: "Smith"}, nil
}
return nil, fmt.Errorf("invalid credentials")
}
func NewValidator(name string, password string) Validator {
return &ValidationRequest{Name: name, Password: password}
}
* Contents of user.go
package user
type Data struct {
UserID string `json:"user_id"`
UserName string `json:"user_name"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
}
* Contents of configure_test.go
package cmd
import (
"localdev/mockexample/mocks"
"localdev/mockexample/user"
"os"
"testing"
"github.com/golang/mock/gomock"
)
func TestConfigure(t *testing.T) {
t.Run("ConfigureWithMock", func(t *testing.T) {
os.Args[1] = "bob"
os.Args[2] = "1234"
ctrl := gomock.NewController(t)
mockValidator := mocks.NewMockValidator(ctrl)
//mockValidator.EXPECT().ValidateUser().AnyTimes() // zero more calls, so this will also pass.
userData := user.Data{UserID: "testId"}
mockValidator.EXPECT().ValidateUser().Return(&userData, nil).Times(1) //(gomock.Any(), gomock.Any()) //(&userData, nil)
Configure()
})
}
Contents of generated mock
// Code generated by MockGen. DO NOT EDIT.
// Source: localdev/mockexample/validator (interfaces: Validator)
// Package mocks is a generated GoMock package.
package mocks
import (
user "localdev/mockexample/user"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockValidator is a mock of Validator interface.
type MockValidator struct {
ctrl *gomock.Controller
recorder *MockValidatorMockRecorder
}
// MockValidatorMockRecorder is the mock recorder for MockValidator.
type MockValidatorMockRecorder struct {
mock *MockValidator
}
// NewMockValidator creates a new mock instance.
func NewMockValidator(ctrl *gomock.Controller) *MockValidator {
mock := &MockValidator{ctrl: ctrl}
mock.recorder = &MockValidatorMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockValidator) EXPECT() *MockValidatorMockRecorder {
return m.recorder
}
// ValidateUser mocks base method.
func (m *MockValidator) ValidateUser() (*user.Data, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ValidateUser")
ret0, _ := ret[0].(*user.Data)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValidateUser indicates an expected call of ValidateUser.
func (mr *MockValidatorMockRecorder) ValidateUser() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateUser", reflect.TypeOf((*MockValidator)(nil).ValidateUser))
}
The root problem is that the function Configure never uses the mock structure, so you get a missing call(s) to *mocks.MockValidator.ValidateUser() error.
In the file configure_test.go, mockValidator is simply not used at all. There must be some kind of injection of that mock in order to be called by the Configure function.
You could make the following changes to fix the test, as an example of what I'm referring to injection. Not saying this is the best approach but I'm trying to make the fewer possible changes to your code.
configure_test.go:
func TestConfigure(t *testing.T) {
t.Run("ConfigureWithMock", func(t *testing.T) {
os.Args[1] = "bob"
os.Args[2] = "1234"
ctrl := gomock.NewController(t)
mockValidator := mocks.NewMockValidator(ctrl)
//mockValidator.EXPECT().ValidateUser().AnyTimes() // zero more calls, so this will also pass.
userData := user.Data{UserID: "testId"}
mockValidator.
EXPECT().
ValidateUser("bob", "1234").
Return(&userData, nil).
Times(1) //(gomock.Any(), gomock.Any()) //(&userData, nil)
Configure(mockValidator)
})
}
configure.go
func Configure(v validator.Validator) {
name := os.Args[1]
password := os.Args[2]
user, err := v.ValidateUser(name, password)
if err != nil {
fmt.Printf("some thing went wrong. %v\n", err)
return
}
fmt.Printf("Credentials are valid. Welcome: %s %s\n", user.FirstName, user.LastName)
}
validator.go
type Validator interface {
ValidateUser(name, password string) (*user.Data, error)
}
type ValidationRequest struct {
Command string
// Name string
// Password string
}
func (vr ValidationRequest) ValidateUser(name, password string) (*user.Data, error) {
if name == "bob" && password == "1234" {
return &user.Data{UserID: "123", UserName: "bsmith", FirstName: "Bob", LastName: "Smith"}, nil
}
return nil, fmt.Errorf("invalid credentials")
}
func NewValidator() Validator {
return &ValidationRequest{}
}
Take into account that you need to generate the mock again. Hope this helps you to understand mock testing.
I've some web-application server using go http and I want that each request will have context with uuid, for this I can use http request context https://golang.org/pkg/net/http/#Request.Context
we are using logrus and we initiate it in one file and use the logger instance in other files.
what I need is to print request ID in all the logs but not to add new paremeters to each log print, I want do to it once in each http request (pass the req-id) and all the logs print will have it without doing anything with it
e.g. if the id=123 so log.info("foo") will print
// id=123 foo
I've tried with the following but not sure it's the right way, please advice.
package main
import (
"context"
"errors"
log "github.com/sirupsen/logrus"
)
type someContextKey string
var (
keyA = someContextKey("a")
keyB = someContextKey("b")
)
func main() {
ctx := context.Background()
ctx = context.WithValue(ctx, keyA, "foo")
ctx = context.WithValue(ctx, keyB, "bar")
logger(ctx, nil).Info("did nothing")
err := errors.New("an error")
logger(ctx, err).Fatal("unrecoverable error")
}
func logger(ctx context.Context, err error) *log.Entry {
entry := log.WithField("component", "main")
entry = entry.WithField("ReqID", "myRequestID")
return entry
}
https://play.golang.org/p/oCW09UhTjZ5
Every time you call the logger function you are creating a new *log.Entry and writing the request ID to it again. From your question it sounded like you do not want that.
func main() {
ctx := context.Background()
ctx = context.WithValue(ctx, keyA, "foo")
ctx = context.WithValue(ctx, keyB, "bar")
lg := logger(ctx)
lg.Info("did nothing")
err := errors.New("an error")
lg.WithError(err).Fatal("unrecoverable error")
}
func logger(ctx context.Context) *log.Entry {
entry := log.WithField("component", "main")
entry = entry.WithField("ReqID", "myRequestID")
return entry
}
The downside of this is that you will have to pass the lg variable to every function this request calls and which should also log the request ID.
What we did at our company is create a thin layer around logrus that has an additional method WithRequestCtx so we could pass in the request context and it would extract the request ID itself (which we had written to the context in a middleware). If no request ID was present nothing was added to the log entry. This however did add the request ID to every log entry again as your sample code also did.
Note: our thin layer around logrus had a lot more functionality and default settings to justify the extra effort. In the long run this turned out very helpful to have one place to be able to adjust logging for all our services.
Note2: meanwhile we are in the process of replacing logrus with zerolog to be more lightweight.
Late answer but all you need to do is just call logrus.WithContext(/* your *http.Request.Context() goes here*/).... in your application and logrus will automatically add "id":"SOME-UUID" to each logs. Design is flexible for extracting more key-value from request context if you wanted to.
initialise logger
package main
import (
"path/to/logger"
"path/to/request"
)
func main() {
err := logger.Setup(logger.Config{
ContextFields: map[string]interface{}{
string(request.CtxIDKey): request.CtxIDKey,
}
})
if err != nil {
// ...
}
}
logger
package logger
import (
"github.com/sirupsen/logrus"
)
type Config struct {
Level string
ContextFields map[string]interface{}
}
func Setup(config Config) error {
lev, err := logrus.ParseLevel(config.Level)
if err != nil {
return err
}
logrus.SetLevel(lev)
return nil
}
func (c Config) Fire(e *logrus.Entry) error {
for k, v := range c.StaticFields {
e.Data[k] = v
}
if e.Context != nil {
for k, v := range c.ContextFields {
if e.Context.Value(v) != nil {
e.Data[k] = e.Context.Value(v).(string)
}
}
}
return nil
}
request
package request
import (
"context"
"net/http"
"github.com/google/uuid"
)
type ctxID string
const CtxIDKey = ctxID("id")
func ID(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), CtxIDKey, uuid.New().String())))
})
}
I'm trying to write a tiny application in Go that can send an HTTP request to all IP addresses in hopes to find a specific content. The issue is that the application seems to crash in a very peculiar way when the call is executed asynchronously.
ip/validator.go
package ip
import (
"io/ioutil"
"net/http"
"regexp"
"time"
)
type ipValidator struct {
httpClient http.Client
path string
exp *regexp.Regexp
confirmationChannel *chan string
}
func (this *ipValidator) validateUrl(url string) bool {
response, err := this.httpClient.Get(url)
if err != nil {
return false
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return false
}
bodyBytes, _ := ioutil.ReadAll(response.Body)
result := this.exp.Match(bodyBytes)
if result && this.confirmationChannel != nil {
*this.confirmationChannel <- url
}
return result
}
func (this *ipValidator) ValidateIp(addr ip) bool {
httpResult := this.validateUrl("http://" + addr.ToString() + this.path)
httpsResult := this.validateUrl("https://" + addr.ToString() + this.path)
return httpResult || httpsResult
}
func (this *ipValidator) GetSuccessChannel() *chan string {
return this.confirmationChannel
}
func NewIpValidadtor(path string, exp *regexp.Regexp) ipValidator {
return newValidator(path, exp, nil)
}
func NewAsyncIpValidator(path string, exp *regexp.Regexp) ipValidator {
c := make(chan string)
return newValidator(path, exp, &c)
}
func newValidator(path string, exp *regexp.Regexp, c *chan string) ipValidator {
httpClient := http.Client{
Timeout: time.Second * 2,
}
return ipValidator{httpClient, path, exp, c}
}
main.go
package main
import (
"./ip"
"fmt"
"os"
"regexp"
)
func processOutput(c *chan string) {
for true {
url := <- *c
fmt.Println(url)
}
}
func main() {
args := os.Args[1:]
fmt.Printf("path: %s regex: %s", args[0], args[1])
regexp, regexpError := regexp.Compile(args[1])
if regexpError != nil {
fmt.Println("The provided regexp is not valid")
return
}
currentIp, _ := ip.NewIp("172.217.22.174")
validator := ip.NewAsyncIpValidator(args[0], regexp)
successChannel := validator.GetSuccessChannel()
go processOutput(successChannel)
for currentIp.HasMore() {
go validator.ValidateIp(currentIp)
currentIp = currentIp.Increment()
}
}
Note the line that says go validator.ValidateIp(currentIp) in main.go. Should I remove the word "go" to execute everything within the main routine, the code works as expected -> it sends requests to IP addresses starting 172.217.22.174 and should one of them return a legitimate result that matches the regexp that the ipValidator was initialized with, the URL is passed to the channel and the value is printed out by processOutput function from main.go. The issue is that simply adding go in front of validator.ValidateIp(currentIp) breaks that functionality. In fact, according to the debugger, I never seem to go past the line that says response, err := this.httpClient.Get(url) in validator.go.
The struggle is real. Should I decide to scan the whole internet, there's 256^4 IP addresses to go through. It will take years, unless I find a way to split the process into multiple routines.
I want to use custom interface based on go plugin, but I found it's not support.
Definition of filter.Filter
package filter
import (
"net/http"
"github.com/valyala/fasthttp"
)
// Context filter context
type Context interface {
SetStartAt(startAt int64)
SetEndAt(endAt int64)
GetStartAt() int64
GetEndAt() int64
GetProxyServerAddr() string
GetProxyOuterRequest() *fasthttp.Request
GetProxyResponse() *fasthttp.Response
NeedMerge() bool
GetOriginRequestCtx() *fasthttp.RequestCtx
GetMaxQPS() int
ValidateProxyOuterRequest() bool
InBlacklist(ip string) bool
InWhitelist(ip string) bool
IsCircuitOpen() bool
IsCircuitHalf() bool
GetOpenToCloseFailureRate() int
GetHalfTrafficRate() int
GetHalfToOpenSucceedRate() int
GetOpenToCloseCollectSeconds() int
ChangeCircuitStatusToClose()
ChangeCircuitStatusToOpen()
RecordMetricsForRequest()
RecordMetricsForResponse()
RecordMetricsForFailure()
RecordMetricsForReject()
GetRecentlyRequestSuccessedCount(sec int) int
GetRecentlyRequestCount(sec int) int
GetRecentlyRequestFailureCount(sec int) int
}
// Filter filter interface
type Filter interface {
Name() string
Pre(c Context) (statusCode int, err error)
Post(c Context) (statusCode int, err error)
PostErr(c Context)
}
// BaseFilter base filter support default implemention
type BaseFilter struct{}
// Pre execute before proxy
func (f BaseFilter) Pre(c Context) (statusCode int, err error) {
return http.StatusOK, nil
}
// Post execute after proxy
func (f BaseFilter) Post(c Context) (statusCode int, err error) {
return http.StatusOK, nil
}
// PostErr execute proxy has errors
func (f BaseFilter) PostErr(c Context) {
}
This pkg is in my go app project.
load plugin file
package proxy
import (
"errors"
"plugin"
"strings"
"github.com/fagongzi/gateway/pkg/conf"
"github.com/fagongzi/gateway/pkg/filter"
)
var (
// ErrKnownFilter known filter error
ErrKnownFilter = errors.New("unknow filter")
)
const (
// FilterHTTPAccess access log filter
FilterHTTPAccess = "HTTP-ACCESS"
// FilterHeader header filter
FilterHeader = "HEAD" // process header fiter
// FilterXForward xforward fiter
FilterXForward = "XFORWARD"
// FilterBlackList blacklist filter
FilterBlackList = "BLACKLIST"
// FilterWhiteList whitelist filter
FilterWhiteList = "WHITELIST"
// FilterAnalysis analysis filter
FilterAnalysis = "ANALYSIS"
// FilterRateLimiting limit filter
FilterRateLimiting = "RATE-LIMITING"
// FilterCircuitBreake circuit breake filter
FilterCircuitBreake = "CIRCUIT-BREAKE"
// FilterValidation validation request filter
FilterValidation = "VALIDATION"
)
func newFilter(filterSpec *conf.FilterSpec) (filter.Filter, error) {
if filterSpec.External {
return newExternalFilter(filterSpec)
}
input := strings.ToUpper(filterSpec.Name)
switch input {
case FilterHTTPAccess:
return newAccessFilter(), nil
case FilterHeader:
return newHeadersFilter(), nil
case FilterXForward:
return newXForwardForFilter(), nil
case FilterAnalysis:
return newAnalysisFilter(), nil
case FilterBlackList:
return newBlackListFilter(), nil
case FilterWhiteList:
return newWhiteListFilter(), nil
case FilterRateLimiting:
return newRateLimitingFilter(), nil
case FilterCircuitBreake:
return newCircuitBreakeFilter(), nil
case FilterValidation:
return newValidationFilter(), nil
default:
return nil, ErrKnownFilter
}
}
func newExternalFilter(filterSpec *conf.FilterSpec) (filter.Filter, error) {
p, err := plugin.Open(filterSpec.ExternalPluginFile)
if err != nil {
return nil, err
}
s, err := p.Lookup("NewExternalFilter")
if err != nil {
return nil, err
}
sf := s.(func() (filter.Filter, error))
return sf()
}
This is the code of load plugin in my go app project
package main
import (
"C"
"strings"
"time"
"github.com/CodisLabs/codis/pkg/utils/log"
"github.com/fagongzi/gateway/pkg/filter"
"github.com/valyala/fasthttp"
)
// AccessFilter record the http access log
// log format: $remoteip "$method $path" $code "$agent" $svr $cost
type AccessFilter struct {
}
// NewExternalFilter create a External filter
func NewExternalFilter() (filter.Filter, error) {
return &AccessFilter{}, nil
}
// Name return name of this filter
func (f *AccessFilter) Name() string {
return "HTTP-ACCESS"
}
// Pre pre process
func (f *AccessFilter) Pre(c filter.Context) (statusCode int, err error) {
return 200, nil
}
// Post execute after proxy
func (f *AccessFilter) Post(c filter.Context) (statusCode int, err error) {
cost := (c.GetStartAt() - c.GetEndAt())
log.Infof("%s %s \"%s\" %d \"%s\" %s %s",
GetRealClientIP(c.GetOriginRequestCtx()),
c.GetOriginRequestCtx().Method(),
c.GetProxyOuterRequest().RequestURI(),
c.GetProxyResponse().StatusCode(),
c.GetOriginRequestCtx().UserAgent(),
c.GetProxyServerAddr(),
time.Duration(cost))
return 200, nil
}
// PostErr post error process
func (f *AccessFilter) PostErr(c filter.Context) {
}
// GetRealClientIP get read client ip
func GetRealClientIP(ctx *fasthttp.RequestCtx) string {
xforward := ctx.Request.Header.Peek("X-Forwarded-For")
if nil == xforward {
return strings.SplitN(ctx.RemoteAddr().String(), ":", 2)[0]
}
return strings.SplitN(string(xforward), ",", 2)[0]
}
This is the definition of plugin, it's in my plugin project. The plugin project and go app project are different projects.
I found errors:
panic: interface conversion: plugin.Symbol is func() (filter.Filter, error), not func() (filter.Filter, error)
You can find code in this project https://github.com/fagongzi/gateway/tree/go18-plugin-support.
filter.Filter is in pkg/filter package.
load plugin file in proxy/factory.go
plugin go file is in another project.
Custom interfaces work just fine.
But one important thing: you can only type assert types from values looked up from plugins that are defined outside of the plugin (you can't refer types defined in plugins). This also applies to each component of "composite types", for example you can only type assert a function type whose parameter and result types are also defined outside of the plugin.
1. With a common package outside of the plugin
One solution is to define the interface in a package outside of the plugin, and both the plugin and your app can import it and refer to it.
Define it in package filter:
package filter
type Filter interface {
Name() string
Age() int
}
The plugin is in package pq and imports package filter:
package main
import (
"fmt"
"filter"
)
type plgFilter struct{}
func (plgFilter) Name() string { return "Bob" }
func (plgFilter) Age() int { return 23 }
func GetFilter() (f filter.Filter, err error) {
f = plgFilter{}
fmt.Printf("[plugin GetFilter] Returning filter: %T %v\n", f, f)
return
}
And the main app that also imports (the same) package filter, loads the plugin, looks up GetFilter(), calls it and also uses the returned Filter:
package main
import (
"fmt"
"filter"
"plugin"
)
func main() {
p, err := plugin.Open("pg/pg.so")
if err != nil {
panic(err)
}
GetFilter, err := p.Lookup("GetFilter")
if err != nil {
panic(err)
}
filter, err := GetFilter.(func() (filter.Filter, error))()
fmt.Printf("GetFilter result: %T %v %v\n", filter, filter, err)
fmt.Println("\tName:", filter.Name())
fmt.Println("\tAge:", filter.Age())
}
Output:
[plugin GetFilter] Returning filter: main.plgFilter {}
GetFilter result: main.plgFilter {} <nil>
Name: Bob
Age: 23
2. With plugin returning interface{}, and interface defined in main app
Another solution is to have the plugin function return a value of type interface{}. Your main app can define the interface it expects, and it can use type assertion on the interface{} value returned by the plugin.
No filter package this time.
The plugin is in package pq:
package main
import (
"fmt"
)
type plgFilter struct{}
func (plgFilter) Name() string { return "Bob" }
func (plgFilter) Age() int { return 23 }
func GetFilterIface() (f interface{}, err error) {
f = plgFilter{}
fmt.Printf("[plugin GetFilterIface] Returning filter: %T %v\n", f, f)
return
}
And the main app:
package main
import (
"fmt"
"plugin"
)
func main() {
p, err := plugin.Open("pg/pg.so")
if err != nil {
panic(err)
}
GetFilterIface, err := p.Lookup("GetFilterIface")
if err != nil {
panic(err)
}
filterIface, err := GetFilterIface.(func() (interface{}, error))()
fmt.Printf("GetFilterIface result: %T %v %v\n", filterIface, filterIface, err)
myfilter := filterIface.(MyFilter)
fmt.Println("\tName:", myfilter.Name())
fmt.Println("\tAge:", myfilter.Age())
}
type MyFilter interface {
Name() string
Age() int
}
Output:
[plugin GetFilterIface] Returning filter: main.plgFilter {}
GetFilterIface result: main.plgFilter {} <nil>
Name: Bob
Age: 23
Also see related question: How do Go plugin dependencies work?
I wrote a functions that save data into redis database server. The challenge is that I want to test these functions and do not know how to test it.
I just start somehow with
Functions
package sessrage
/*
* Save data into redis database. In the common case,
* the data will be only valid during a request. Use
* hash datatype in redis.
*/
import (
"../context"
"github.com/garyburd/redigo/redis"
"net/http"
)
const (
protocol string = "tcp"
port string = ":6379"
)
func connectAndCloseRedis(connectCall func(con redis.Conn)) {
c, err := redis.Dial("tcp", ":6379")
defer c.Close()
if err != nil {
panic(err.Error())
}
connectCall(c)
}
func PostSessionData(r *http.Request, key, value string) {
go connectAndCloseRedis(func(con redis.Conn) {
sessionId := context.Get(r, context.JwtId).(string)
con.Do("HMSET", sessionId, key, value)
})
}
func GetSessionData(r *http.Request, key string) interface{} {
var result interface{}
sessionId := context.Get(r, context.JwtId).(string)
reply, _ := redis.Values(c.Do("HMGET", sessionId, key))
redis.Scan(reply, &result)
return result
}
and the test file
package sessrage
import (
//"fmt"
"../context"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
"time"
)
var server *httptest.Server
var glrw http.ResponseWriter
var glr *http.Request
func init() {
server = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
glrw = rw
glr = r
context.Set(glr, context.JwtId, "TestId")
}))
}
func TestPostAndGetSession(t *testing.T) {
Convey("POST and GET data on redis.", t, func() {
PostSessionData(glr, "key1", "value1")
time.Sleep(time.Second * 10)
v := GetSessionData(glr, "key1")
assert.Equal(t, "value1", v)
})
}
when I try to run the test I've got
an't load package: ......./sessrage.go:10:2: local import "../context" in non-local package
and the context package looks like
package context
import (
"github.com/gorilla/context"
"net/http"
)
type contextKey int
const (
LanguageId contextKey = iota
JwtId
)
func Get(r *http.Request, key interface{}) interface{} {
return context.Get(r, key)
}
func Set(r *http.Request, key, val interface{}) {
context.Set(r, key, val)
}
What do I wrong?
That is the first time, I am testing code in conjunction with http. It seems to be very hard to test.
There are a few issues:
Don't use relative import paths.
Use a pool instead of dialing redis on every action.
The call to sessionId := context.Get(r, context.JwtId).(string) in the PostSessionData anonymous function can fail if the mux or something higher in the call chain clears the Gorilla context before the goroutine runs. Do this instead:
func PostSessionData(r *http.Request, key, value string) {
c := pool.Get()
defer c.Close()
sessionId := context.Get(r, context.JwtId).(string)
if err := c.Do("HMSET", sessionId, key, value); err != nil {
// handle error
}
}