Skip to content

Commit

Permalink
Merge ba8e32a into 6f69a3a
Browse files Browse the repository at this point in the history
  • Loading branch information
msohailhussain committed Dec 16, 2019
2 parents 6f69a3a + ba8e32a commit 1fe96ec
Show file tree
Hide file tree
Showing 13 changed files with 362 additions and 63 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ jobs:

- stage: 'Integration tests'
env: GIMME_GO_VERSION=1.12.x FSC_PATH="/tmp/fsc-repo"
language: node_js
before_script:
- mkdir -p $FSC_PATH
- pushd $FSC_PATH && git init && git fetch --depth=1 https://$CI_USER_TOKEN@github.com/optimizely/fullstack-sdk-compatibility-suite ${FSC_BRANCH:-master} && git checkout FETCH_HEAD && popd
- ./scripts/setup-integration-tests.sh
install:
- eval "$(gimme)"
script:
Expand Down
3 changes: 2 additions & 1 deletion pkg/config/polling_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ func (cm *PollingProjectConfigManager) SyncConfig(datafile []byte) {
cm.configLock.Unlock()
}

var hardcodedDatafile = datafile
url := fmt.Sprintf(cm.datafileURLTemplate, cm.sdkKey)
if len(datafile) == 0 {
if cm.lastModified != "" {
Expand Down Expand Up @@ -159,7 +160,7 @@ func (cm *PollingProjectConfigManager) SyncConfig(datafile []byte) {
cm.projectConfig = projectConfig
closeMutex(nil)

if cm.notificationCenter != nil {
if cm.notificationCenter != nil && len(hardcodedDatafile) == 0 {
projectConfigUpdateNotification := notification.ProjectConfigUpdateNotification{
Type: notification.ProjectConfigUpdate,
Revision: cm.projectConfig.GetRevision(),
Expand Down
33 changes: 24 additions & 9 deletions pkg/config/polling_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,38 +202,53 @@ func TestNewPollingProjectConfigManagerWithErrorHandling(t *testing.T) {
func TestNewPollingProjectConfigManagerOnDecision(t *testing.T) {
mockDatafile1 := []byte(`{"revision":"42","botFiltering":true}`)
mockDatafile2 := []byte(`{"revision":"43","botFiltering":false}`)
mockDatafile3 := []byte(`{"revision":"44","botFiltering":false}`)
projectConfig1, _ := datafileprojectconfig.NewDatafileProjectConfig(mockDatafile1)
projectConfig2, _ := datafileprojectconfig.NewDatafileProjectConfig(mockDatafile2)
projectConfig3, _ := datafileprojectconfig.NewDatafileProjectConfig(mockDatafile3)

mockRequester := new(MockRequester)
mockRequester.On("Get", []utils.Header(nil)).Return(mockDatafile1, http.Header{}, http.StatusOK, nil)
mockRequester.On("Get", []utils.Header(nil)).Return(mockDatafile2, http.Header{}, http.StatusOK, nil)

// Test we fetch using requester
sdkKey := "test_sdk_key"

eg := newExecGroup()
configManager := NewPollingProjectConfigManager(sdkKey, WithRequester(mockRequester))
configManager := NewPollingProjectConfigManager(sdkKey, WithRequester(mockRequester), WithInitialDatafile(mockDatafile1))
eg.Go(configManager.Start)

var numberOfCalls = 0
callback := func(notification notification.ProjectConfigUpdateNotification) {
numberOfCalls++
}
id, _ := configManager.OnProjectConfigUpdate(callback)
mockRequester.AssertExpectations(t)
assert.NotEqual(t, id, 0)

// Verify config was updated with provided hardcoded mockDatafile1
actual, err := configManager.GetConfig()
assert.Nil(t, err)
assert.NotNil(t, actual)
assert.Equal(t, projectConfig1, actual)
// Verify listener was not called for hardcoded mockDatafile1
assert.Equal(t, numberOfCalls, 0)

configManager.SyncConfig(mockDatafile2)
// Syncconfig with empty datafile to fetch remotely
configManager.SyncConfig([]byte{})
actual, err = configManager.GetConfig()
assert.Nil(t, err)
assert.NotNil(t, actual)

assert.NotEqual(t, id, 0)
// Verify config was updated remotely with mockDatafile2
assert.Equal(t, projectConfig2, actual)
mockRequester.AssertExpectations(t)
// Verify listener was called for remote datafile update
assert.Equal(t, numberOfCalls, 1)

err = configManager.RemoveOnProjectConfigUpdate(id)
// Syncconfig with hardcoded mockDatafile3
configManager.SyncConfig(mockDatafile3)
actual, err = configManager.GetConfig()
assert.Nil(t, err)
// Verify config was updated with provided hardcoded mockDatafile3
assert.Equal(t, projectConfig3, actual)
// Verify listener was not called for hardcoded mockDatafile3
assert.Equal(t, numberOfCalls, 1)

err = configManager.RemoveOnProjectConfigUpdate(id)
assert.Nil(t, err)
Expand Down
9 changes: 9 additions & 0 deletions scripts/setup-integration-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash

. ~/.nvm/nvm.sh
mkdir -p $FSC_PATH
pushd $FSC_PATH && git init && git fetch --depth=1 https://$CI_USER_TOKEN@github.com/optimizely/fullstack-sdk-compatibility-suite ${FSC_BRANCH:-master} && git checkout FETCH_HEAD
ln -s features/support/datafiles/ public
pushd services/datafile && nvm install && nvm use && npm install && popd
node services/datafile/ &> /dev/null &
popd
2 changes: 2 additions & 0 deletions tests/integration/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ func FeatureContext(s *godog.Suite) {
s.Step(`^(\d+) "([^"]*)" listener is added$`, context.ListenerIsAdded)
s.Step(`^the User Profile Service is "([^"]*)"$`, context.TheUserProfileServiceIs)
s.Step(`^user "([^"]*)" has mapping "([^"]*)": "([^"]*)" in User Profile Service$`, context.UserHasMappingInUserProfileService)
s.Step(`^a datafile manager with the configuration$`, context.DatafileManagerConfigurationIs)
s.Step(`^in the response, the "([^"]*)" listener was called (\d+) times$`, context.TheListenerWasCalledNTimes)
s.Step(`^([^\\\"]*) is called with arguments$`, context.IsCalledWithArguments)
s.Step(`^the result should be (?:string )?"([^"]*)"$`, context.TheResultShouldBeString)
s.Step(`^the result should be (?:integer )?(\d+)$`, context.TheResultShouldBeInteger)
Expand Down
1 change: 1 addition & 0 deletions tests/integration/models/api_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type APIOptions struct {
DatafileName string
APIName string
Arguments string
DFMConfiguration *DataFileManagerConfiguration
Listeners map[string]int
UserProfileServiceType string
UPSMapping map[string]map[string]string
Expand Down
9 changes: 9 additions & 0 deletions tests/integration/models/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ const (
GetForcedVariation SDKAPI = "get_forced_variation"
)

// KeyWaitForOnReady - Key for on-ready waiting condition
const KeyWaitForOnReady = "wait_for_on_ready"

// KeyWaitForConfigUpdate - Key for config-update waiting condition
const KeyWaitForConfigUpdate = "wait_for_config_update"

// KeyListenerCalled - Key for listener called
const KeyListenerCalled = "listener_called"

Expand All @@ -75,3 +81,6 @@ const KeyDecision = "Decision"

// KeyTrack - Key for Track listener
const KeyTrack = "Track"

// KeyConfigUpdate - Key for Config Update listener
const KeyConfigUpdate = "Config-update"
27 changes: 27 additions & 0 deletions tests/integration/models/datafilemanager_configuration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/****************************************************************************
* Copyright 2019, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
* You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
***************************************************************************/

package models

// DataFileManagerConfiguration represents a datafile manager configuration
type DataFileManagerConfiguration struct {
SDKKey string `yaml:"sdk_key"`
Mode string `yaml:"mode,omitempty"`
Revision *int `yaml:"revision,omitempty"`
DatafileCondition string `yaml:"datafile_condition,omitempty"`
UpdateInterval *int `yaml:"update_interval,omitempty"`
Timeout *int `yaml:"timeout,omitempty"`
}
60 changes: 49 additions & 11 deletions tests/integration/optlyplugins/notification_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package optlyplugins

import (
"time"

"github.com/optimizely/go-sdk/pkg/client"
"github.com/optimizely/go-sdk/pkg/decision"
"github.com/optimizely/go-sdk/pkg/entities"
Expand All @@ -25,9 +27,13 @@ import (
"github.com/optimizely/go-sdk/tests/integration/models"
)

// DefaultInitializationTimeout defines default timeout for datafile sync
const DefaultInitializationTimeout = time.Duration(3500) * time.Millisecond

// NotificationManager manager class for notification listeners
type NotificationManager struct {
listenersCalled []interface{}
listenersCalled []interface{}
projectConfigUpdateListenersCalled []notification.ProjectConfigUpdateNotification
}

// SubscribeNotifications subscribes to the provided notification listeners
Expand All @@ -51,6 +57,44 @@ func (n *NotificationManager) SubscribeNotifications(listeners map[string]int, c
}
}

// SubscribeProjectConfigUpdateNotifications subscribes to the projectConfigUpdate notification listeners
func (n *NotificationManager) SubscribeProjectConfigUpdateNotifications(sdkKey string, listeners map[string]int) {

// This is handled in a way that notification for config update shouldn't be lost.
// OnConfigUpdate in PollingConfigManager can't be used otherwise it will trigger notification during object intiailization.
handler := func(payload interface{}) {
if notification, ok := payload.(notification.ProjectConfigUpdateNotification); ok {
n.projectConfigUpdateListenersCalled = append(n.projectConfigUpdateListenersCalled, notification)
}
}

if count, ok := listeners[models.KeyConfigUpdate]; ok {
for i := 0; i < count; i++ {
registerNotification(sdkKey, notification.ProjectConfigUpdate, handler)
}
}
}

// GetListenersCalled - Returns listeners called
func (n *NotificationManager) GetListenersCalled() []interface{} {
listenerCalled := n.listenersCalled
// Since for every scenario, a new sdk instance is created, emptying listenersCalled is required for scenario's
// where multiple requests are executed but no session is to be maintained among them.
// @TODO: Make it optional once event-batching(sessioned) tests are implemented.
n.listenersCalled = nil
return listenerCalled
}

// GetConfigUpdateListenersCalled - Returns ProjectConfigUpdate listeners called
func (n *NotificationManager) GetConfigUpdateListenersCalled() []notification.ProjectConfigUpdateNotification {
projectConfigUpdateListenersCalled := n.projectConfigUpdateListenersCalled
// Since for every scenario, a new sdk instance is created, emptying listenersCalled is required for scenario's
// where multiple requests are executed but no session is to be maintained among them.
// @TODO: Make it optional once event-batching(sessioned) tests are implemented.
n.projectConfigUpdateListenersCalled = nil
return projectConfigUpdateListenersCalled
}

func (n *NotificationManager) decisionCallback(notification notification.DecisionNotification) {

model := models.DecisionListener{}
Expand All @@ -77,6 +121,10 @@ func (n *NotificationManager) trackCallback(eventKey string, userContext entitie
n.listenersCalled = append(n.listenersCalled, listener)
}

func (n *NotificationManager) configUpdateCallback(notification notification.ProjectConfigUpdateNotification) {
n.projectConfigUpdateListenersCalled = append(n.projectConfigUpdateListenersCalled, notification)
}

func getDecisionInfoForNotification(decisionNotification notification.DecisionNotification) map[string]interface{} {
decisionInfoDict := make(map[string]interface{})

Expand Down Expand Up @@ -138,13 +186,3 @@ func getDecisionInfoForNotification(decisionNotification notification.DecisionNo
}
return decisionInfoDict
}

// GetListenersCalled - Returns listeners called
func (n *NotificationManager) GetListenersCalled() []interface{} {
listenerCalled := n.listenersCalled
// Since for every scenario, a new sdk instance is created, emptying listenersCalled is required for scenario's
// where multiple requests are executed but no session is to be maintained among them.
// @TODO: Make it optional once event-batching(sessioned) tests are implemented.
n.listenersCalled = nil
return listenerCalled
}
2 changes: 1 addition & 1 deletion tests/integration/optlyplugins/proxy_dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (d *ProxyEventDispatcher) DispatchEvent(event event.LogEvent) (bool, error)
return true, nil
}

// DispatchEvent dispatches event with callback
// GetMetrics is the metric accessor
func (d *ProxyEventDispatcher) GetMetrics() event.Metrics {
return nil
}
Expand Down
Loading

0 comments on commit 1fe96ec

Please sign in to comment.