Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
test: SHELL:=/bin/bash
test:
mkdir -p coverage
go test -v -race ./... -coverprofile coverage/cover.out.tmp -coverpkg=./... -run .*
go test -race -coverpkg=./... ./... -coverprofile cover.out.tmp -covermode=atomic
cat coverage/cover.out.tmp | grep -v "mock_\|examples" > coverage/cover.out
go tool cover -html=coverage/cover.out -o coverage/cover.html
go tool cover -func=coverage/cover.out
Expand Down
34 changes: 17 additions & 17 deletions cmd/server/connectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,46 @@ import (

"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/flagship-io/decision-api/pkg/config"
"github.com/flagship-io/decision-api/pkg/connectors"
"github.com/flagship-io/decision-api/pkg/connectors/assignments_managers"
"github.com/flagship-io/decision-api/pkg/utils/config"
"github.com/flagship-io/decision-api/pkg/utils/logger"
"github.com/flagship-io/decision-api/pkg/logger"
)

func getAssignmentsManager(cfg *config.Config) (assignmentsManager connectors.AssignmentsManager, err error) {
switch cfg.GetStringDefault("cache.type", "") {
switch cfg.GetDefaultString("cache.type", "") {
case "memory":
assignmentsManager = assignments_managers.InitMemoryManager()
case "local":
assignmentsManager, err = assignments_managers.InitLocalCacheManager(assignments_managers.LocalOptions{
DbPath: cfg.GetStringDefault("cache.options.dbpath", "cache_data"),
DbPath: cfg.GetDefaultString("cache.options.dbpath", "cache_data"),
})
case "redis":
var tlsConfig *tls.Config
if cfg.GetBool("cache.options.redisTls") {
tlsConfig = &tls.Config{}
}
assignmentsManager, err = assignments_managers.InitRedisManager(assignments_managers.RedisOptions{
Host: cfg.GetStringDefault("cache.options.redisHost", "localhost:6379"),
Username: cfg.GetStringDefault("cache.options.redisUsername", ""),
Password: cfg.GetStringDefault("cache.options.redisPassword", ""),
Db: cfg.GetIntDefault("cache.options.redisDb", 0),
TTL: cfg.GetDurationDefault("cache.options.redisTtl", 3*30*24*time.Hour),
LogLevel: cfg.GetStringDefault("log.level", config.LoggerLevel),
LogFormat: logger.LogFormat(cfg.GetStringDefault("log.format", config.LoggerFormat)),
Host: cfg.GetDefaultString("cache.options.redisHost", "localhost:6379"),
Username: cfg.GetDefaultString("cache.options.redisUsername", ""),
Password: cfg.GetDefaultString("cache.options.redisPassword", ""),
Db: cfg.GetDefaultInt("cache.options.redisDb", 0),
TTL: cfg.GetDefaultDuration("cache.options.redisTtl", 3*30*24*time.Hour),
LogLevel: cfg.GetDefaultString("log.level", config.LoggerLevel),
LogFormat: logger.LogFormat(cfg.GetDefaultString("log.format", config.LoggerFormat)),
TLSConfig: tlsConfig,
})
case "dynamo":
session, _ := session.NewSession()
client := dynamodb.New(session)
assignmentsManager = assignments_managers.InitDynamoManager(assignments_managers.DynamoManagerOptions{
Client: client,
TableName: cfg.GetStringDefault("cache.options.dynamoTableName", "visitor-assignments"),
PrimaryKeySeparator: cfg.GetStringDefault("cache.options.dynamoPKSeparator", "."),
PrimaryKeyField: cfg.GetStringDefault("cache.options.dynamoPKField", "id"),
GetItemTimeout: cfg.GetDurationDefault("cache.options.dynamoGetTimeout", 1*time.Second),
LogLevel: cfg.GetStringDefault("log.level", config.LoggerLevel),
LogFormat: logger.LogFormat(cfg.GetStringDefault("log.format", config.LoggerFormat)),
TableName: cfg.GetDefaultString("cache.options.dynamoTableName", "visitor-assignments"),
PrimaryKeySeparator: cfg.GetDefaultString("cache.options.dynamoPKSeparator", "."),
PrimaryKeyField: cfg.GetDefaultString("cache.options.dynamoPKField", "id"),
GetItemTimeout: cfg.GetDefaultDuration("cache.options.dynamoGetTimeout", 1*time.Second),
LogLevel: cfg.GetDefaultString("log.level", config.LoggerLevel),
LogFormat: logger.LogFormat(cfg.GetDefaultString("log.format", config.LoggerFormat)),
})
default:
assignmentsManager = &assignments_managers.EmptyManager{}
Expand Down
2 changes: 1 addition & 1 deletion cmd/server/connectors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"testing"

"github.com/alicebob/miniredis/v2"
"github.com/flagship-io/decision-api/pkg/config"
"github.com/flagship-io/decision-api/pkg/connectors/assignments_managers"
"github.com/flagship-io/decision-api/pkg/utils/config"
"github.com/stretchr/testify/assert"
)

Expand Down
18 changes: 9 additions & 9 deletions cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,26 @@ import (
"syscall"
"time"

"github.com/flagship-io/decision-api/pkg/config"
"github.com/flagship-io/decision-api/pkg/connectors/environment_loaders"
"github.com/flagship-io/decision-api/pkg/connectors/hits_processors"
"github.com/flagship-io/decision-api/pkg/logger"
"github.com/flagship-io/decision-api/pkg/models"
"github.com/flagship-io/decision-api/pkg/server"
"github.com/flagship-io/decision-api/pkg/utils/config"
"github.com/flagship-io/decision-api/pkg/utils/logger"
)

var shutdownTimeout = 3 * time.Second

func createLogger(cfg *config.Config) *logger.Logger {
lvl := cfg.GetStringDefault("log.level", config.LoggerLevel)
format := cfg.GetStringDefault("log.format", config.LoggerFormat)
lvl := cfg.GetDefaultString("log.level", config.LoggerLevel)
format := cfg.GetDefaultString("log.format", config.LoggerFormat)

return logger.New(lvl, logger.LogFormat(format), "Server")
}

func createServer(cfg *config.Config, log *logger.Logger) (*server.Server, error) {
logLvl := cfg.GetStringDefault("log.level", config.LoggerLevel)
logFmt := cfg.GetStringDefault("log.format", config.LoggerFormat)
logLvl := cfg.GetDefaultString("log.level", config.LoggerLevel)
logFmt := cfg.GetDefaultString("log.format", config.LoggerFormat)

log.Info("initializing assignment cache manager from configuration")
assignmentManager, err := getAssignmentsManager(cfg)
Expand All @@ -50,8 +50,8 @@ func createServer(cfg *config.Config, log *logger.Logger) (*server.Server, error
server.WithAssignmentsManager(assignmentManager),
server.WithCorsOptions(&models.CorsOptions{
Enabled: cfg.GetBool("cors.enabled"),
AllowedOrigins: cfg.GetStringDefault("cors.allowed_origins", config.ServerCorsAllowedOrigins),
AllowedHeaders: cfg.GetStringDefault("cors.allowed_headers", config.ServerCorsAllowedHeaders),
AllowedOrigins: cfg.GetDefaultString("cors.allowed_origins", config.ServerCorsAllowedOrigins),
AllowedHeaders: cfg.GetDefaultString("cors.allowed_headers", config.ServerCorsAllowedHeaders),
}),
)
}
Expand All @@ -74,7 +74,7 @@ func main() {

// Run server
go func() {
logger.Infof("Flagship Decision API server [%s] listening on %s", models.Version, cfg.GetStringDefault("address", ":8080"))
logger.Infof("Flagship Decision API server [%s] listening on %s", models.Version, cfg.GetDefaultString("address", ":8080"))
if err := srv.Listen(); err != http.ErrServerClosed {
logger.Fatalf("error when starting server: %v", err)
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"testing"
"time"

"github.com/flagship-io/decision-api/pkg/utils/config"
"github.com/flagship-io/decision-api/pkg/config"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -38,6 +38,7 @@ func TestCreateServer(t *testing.T) {
func TestMain(t *testing.T) {
os.Setenv("API_KEY", "api_key")
os.Setenv("ENV_ID", "env_id")
os.Setenv("ADDRESS", "localhost:8080")
go func() {
time.Sleep(2 * time.Second)
err := syscall.Kill(syscall.Getpid(), syscall.SIGINT)
Expand Down
15 changes: 8 additions & 7 deletions internal/apilogic/campaigns.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import (
"sync"

"github.com/flagship-io/decision-api/internal/handle"
"github.com/flagship-io/decision-api/internal/utils"
"github.com/flagship-io/decision-api/internal/reswriter"
"github.com/flagship-io/decision-api/internal/udc"
"github.com/flagship-io/decision-api/pkg/connectors"
"github.com/flagship-io/decision-api/pkg/models"
common "github.com/flagship-io/flagship-common"
Expand All @@ -19,7 +20,7 @@ import (
func HandleCampaigns(w http.ResponseWriter, req *http.Request, decisionContext *connectors.DecisionContext, handleDecision func(http.ResponseWriter, *handle.Request, error), tracker *common.Tracker) {
handleRequest, err := BuildHandleRequest(req)
if err != nil {
utils.WriteClientError(w, http.StatusBadRequest, err.Error())
reswriter.WriteClientError(w, http.StatusBadRequest, err.Error())
return
}

Expand All @@ -34,10 +35,10 @@ func HandleCampaigns(w http.ResponseWriter, req *http.Request, decisionContext *

if err != nil {
if errors.Is(err, models.ErrEnvironmentNotFound) {
utils.WriteClientError(w, http.StatusBadRequest, fmt.Sprintf("environment %s not found", handleRequest.DecisionContext.EnvID))
reswriter.WriteClientError(w, http.StatusBadRequest, fmt.Sprintf("environment %s not found", handleRequest.DecisionContext.EnvID))
return
}
utils.WriteServerError(w, err)
reswriter.WriteServerError(w, err)
return
}

Expand All @@ -53,15 +54,15 @@ func HandleCampaigns(w http.ResponseWriter, req *http.Request, decisionContext *
}

if len(filteredCampaigns) == 0 {
utils.WriteClientError(w, http.StatusBadRequest, fmt.Sprintf("The campaign %s is paused or doesn’t exist. Verify your customId or campaignId.", handleRequest.CampaignID))
reswriter.WriteClientError(w, http.StatusBadRequest, fmt.Sprintf("The campaign %s is paused or doesn’t exist. Verify your customId or campaignId.", handleRequest.CampaignID))
return
}
handleRequest.Environment.Common.Campaigns = filteredCampaigns
}

// 3. Return panic response is panic mode activated
if handleRequest.Environment.Common.IsPanic {
utils.WritePanicResponse(w, handleRequest.DecisionRequest.VisitorId)
reswriter.WritePanicResponse(w, handleRequest.DecisionRequest.VisitorId)
return
}

Expand Down Expand Up @@ -110,7 +111,7 @@ func HandleCampaigns(w http.ResponseWriter, req *http.Request, decisionContext *
}

func fillVisitorContext(request *handle.Request) error {
data, err := utils.FetchVisitorData(request.DecisionContext.EnvID, request.DecisionRequest.VisitorId.Value)
data, err := udc.FetchVisitorData(request.DecisionContext.EnvID, request.DecisionRequest.VisitorId.Value)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions internal/apilogic/handle_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import (
"strings"

"github.com/flagship-io/decision-api/internal/handle"
"github.com/flagship-io/decision-api/internal/utils"
"github.com/flagship-io/decision-api/internal/parser"
"github.com/flagship-io/flagship-common/targeting"
)

// BuildHandleRequest builds a handle.Request object from the API Gateway request
func BuildHandleRequest(req *http.Request) (*handle.Request, error) {
handleRequest := handle.NewRequestFromHTTP(req)
decisionRequest, err := utils.GetDecisionRequest(req)
decisionRequest, err := parser.ParseRequest(req)

if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion internal/handle/handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"time"

"github.com/flagship-io/decision-api/pkg/connectors"
"github.com/flagship-io/decision-api/pkg/logger"
"github.com/flagship-io/decision-api/pkg/models"
"github.com/flagship-io/decision-api/pkg/utils/logger"
common "github.com/flagship-io/flagship-common"
"github.com/flagship-io/flagship-common/targeting"

Expand Down
40 changes: 20 additions & 20 deletions internal/handle/handle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package handle
import (
"testing"

"github.com/flagship-io/decision-api/internal/utils"
"github.com/flagship-io/decision-api/internal/test"
"github.com/flagship-io/decision-api/pkg/connectors"
"github.com/flagship-io/decision-api/pkg/connectors/hits_processors"
"github.com/flagship-io/decision-api/pkg/models"
Expand Down Expand Up @@ -82,11 +82,11 @@ func TestDecision(t *testing.T) {
clientID := "client_id"

campaigns := []*common.Campaign{
utils.CreateABCampaignMock(
test.CreateABCampaignMock(
"campaign1",
"vg1",
utils.CreateAllUsersTargetingMock(),
utils.CreateModification("key", "value", decision_response.ModificationsType_FLAG),
test.CreateAllUsersTargetingMock(),
test.CreateModification("key", "value", decision_response.ModificationsType_FLAG),
),
}
clientInfos := common.Environment{
Expand Down Expand Up @@ -129,17 +129,17 @@ func TestDecision1Vis1Test(t *testing.T) {
clientID := "client_id"

campaigns := []*common.Campaign{
utils.CreateABCampaignMock(
test.CreateABCampaignMock(
"campaign1",
"vg1",
utils.CreateAllUsersTargetingMock(),
utils.CreateModification("key", "value", decision_response.ModificationsType_FLAG),
test.CreateAllUsersTargetingMock(),
test.CreateModification("key", "value", decision_response.ModificationsType_FLAG),
),
utils.CreateABCampaignMock(
test.CreateABCampaignMock(
"campaign1bis",
"vg1bis",
utils.CreateAllUsersTargetingMock(),
utils.CreateModification("key", "value", decision_response.ModificationsType_FLAG),
test.CreateAllUsersTargetingMock(),
test.CreateModification("key", "value", decision_response.ModificationsType_FLAG),
),
}
clientInfos := common.Environment{
Expand Down Expand Up @@ -191,11 +191,11 @@ func TestDecisionNoReconciliation(t *testing.T) {
}

campaigns := []*common.Campaign{
utils.CreateABCampaignMock(
test.CreateABCampaignMock(
"campaign2",
"vg2",
utils.CreateAllUsersTargetingMock(),
utils.CreateModification("key", "value", decision_response.ModificationsType_FLAG),
test.CreateAllUsersTargetingMock(),
test.CreateModification("key", "value", decision_response.ModificationsType_FLAG),
),
}

Expand Down Expand Up @@ -237,7 +237,7 @@ func TestDecisionReconciliation(t *testing.T) {
anonymousID := "1234"
clientID := "client_id"
handleRequest := Request{
DecisionContext: utils.CreateMockDecisionContext(),
DecisionContext: test.CreateMockDecisionContext(),
DecisionRequest: &decision_request.DecisionRequest{
VisitorId: &wrapperspb.StringValue{Value: anonymousID},
TriggerHit: &wrapperspb.BoolValue{Value: false},
Expand All @@ -250,11 +250,11 @@ func TestDecisionReconciliation(t *testing.T) {
}

campaigns := []*common.Campaign{
utils.CreateABCampaignMock(
test.CreateABCampaignMock(
"campaign3",
"vg3",
utils.CreateAllUsersTargetingMock(),
utils.CreateModification("key", "value", decision_response.ModificationsType_FLAG),
test.CreateAllUsersTargetingMock(),
test.CreateModification("key", "value", decision_response.ModificationsType_FLAG),
),
}

Expand Down Expand Up @@ -307,11 +307,11 @@ func TestDecisionReconciliation(t *testing.T) {
"age": structpb.NewStringValue("21"),
}
campaigns = []*common.Campaign{
utils.CreateABCampaignMock(
test.CreateABCampaignMock(
"campaign4",
"vg3",
utils.CreateTargetingWithProvider(),
utils.CreateModification("key", "value", decision_response.ModificationsType_FLAG),
test.CreateTargetingWithProvider(),
test.CreateModification("key", "value", decision_response.ModificationsType_FLAG),
),
}
clientInfos.Campaigns = campaigns
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package utils
package parser

import (
"errors"
Expand All @@ -10,25 +10,25 @@ import (
"github.com/stretchr/testify/assert"
)

func TestGetDecisionRequest(t *testing.T) {
_, err := GetDecisionRequest(&http.Request{
func TestParseRequest(t *testing.T) {
_, err := ParseRequest(&http.Request{
Method: "GET",
})
assert.Equal(t, errors.New("only POST http method is allowed"), err)

_, err = GetDecisionRequest(&http.Request{
_, err = ParseRequest(&http.Request{
Method: "POST",
Body: io.NopCloser(strings.NewReader("")),
})
assert.Contains(t, err.Error(), "Must be a valid json")

_, err = GetDecisionRequest(&http.Request{
_, err = ParseRequest(&http.Request{
Method: "POST",
Body: io.NopCloser(strings.NewReader("{\"wrong_key\":true}")),
})
assert.Contains(t, err.Error(), "json body is not valid")

r, err := GetDecisionRequest(&http.Request{
r, err := ParseRequest(&http.Request{
Method: "POST",
Body: io.NopCloser(strings.NewReader("{}")),
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package utils
package parser

import (
"encoding/json"
Expand All @@ -13,7 +13,7 @@ import (
)

// GetDecisionRequest transforms http request into a DecisionRequest
func GetDecisionRequest(r *http.Request) (*decision_request.DecisionRequest, error) {
func ParseRequest(r *http.Request) (*decision_request.DecisionRequest, error) {
decisionRequest, err := unmarshalHit(r)

if err != nil {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package utils
package reswriter

import (
"encoding/json"
Expand Down
Loading