github.com/prebid/prebid-server/v2@v2.18.0/stored_requests/config/config_test.go (about)

     1  package config
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"regexp"
    10  	"testing"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  
    14  	sqlmock "github.com/DATA-DOG/go-sqlmock"
    15  	"github.com/julienschmidt/httprouter"
    16  	"github.com/prebid/prebid-server/v2/config"
    17  	"github.com/prebid/prebid-server/v2/metrics"
    18  	"github.com/prebid/prebid-server/v2/stored_requests"
    19  	"github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider"
    20  	"github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher"
    21  	"github.com/prebid/prebid-server/v2/stored_requests/backends/http_fetcher"
    22  	"github.com/prebid/prebid-server/v2/stored_requests/events"
    23  	httpEvents "github.com/prebid/prebid-server/v2/stored_requests/events/http"
    24  	"github.com/stretchr/testify/mock"
    25  )
    26  
    27  func typedConfig(dataType config.DataType, sr *config.StoredRequests) *config.StoredRequests {
    28  	sr.SetDataType(dataType)
    29  	return sr
    30  }
    31  
    32  func isEmptyCacheType(cache stored_requests.CacheJSON) bool {
    33  	cache.Save(context.Background(), map[string]json.RawMessage{"foo": json.RawMessage("true")})
    34  	objs := cache.Get(context.Background(), []string{"foo"})
    35  	return len(objs) == 0
    36  }
    37  
    38  func isMemoryCacheType(cache stored_requests.CacheJSON) bool {
    39  	cache.Save(context.Background(), map[string]json.RawMessage{"foo": json.RawMessage("true")})
    40  	objs := cache.Get(context.Background(), []string{"foo"})
    41  	return len(objs) == 1
    42  }
    43  
    44  func TestNewEmptyFetcher(t *testing.T) {
    45  
    46  	type testCase struct {
    47  		config       *config.StoredRequests
    48  		emptyFetcher bool
    49  		description  string
    50  	}
    51  	testCases := []testCase{
    52  		{
    53  			config:       &config.StoredRequests{},
    54  			emptyFetcher: true,
    55  			description:  "If the config is empty, an EmptyFetcher should be returned",
    56  		},
    57  		{
    58  			config: &config.StoredRequests{
    59  				Database: config.DatabaseConfig{
    60  					ConnectionInfo: config.DatabaseConnection{
    61  						Driver: "postgres",
    62  					},
    63  					CacheInitialization: config.DatabaseCacheInitializer{
    64  						Query: "test query",
    65  					},
    66  					PollUpdates: config.DatabaseUpdatePolling{
    67  						Query: "test poll query",
    68  					},
    69  					FetcherQueries: config.DatabaseFetcherQueries{
    70  						QueryTemplate: "",
    71  					},
    72  				},
    73  			},
    74  			emptyFetcher: true,
    75  			description:  "If Database fetcher query is not defined, but Database Cache init query and Database update polling query are defined EmptyFetcher should be returned",
    76  		},
    77  		{
    78  			config: &config.StoredRequests{
    79  				Database: config.DatabaseConfig{
    80  					ConnectionInfo: config.DatabaseConnection{
    81  						Driver: "postgres",
    82  					},
    83  					CacheInitialization: config.DatabaseCacheInitializer{
    84  						Query: "",
    85  					},
    86  					PollUpdates: config.DatabaseUpdatePolling{
    87  						Query: "",
    88  					},
    89  					FetcherQueries: config.DatabaseFetcherQueries{
    90  						QueryTemplate: "test fetcher query",
    91  					},
    92  				},
    93  			},
    94  			emptyFetcher: false,
    95  			description:  "If Database fetcher query is defined, but Database Cache init query and Database update polling query are not defined not EmptyFetcher (DBFetcher) should be returned",
    96  		},
    97  		{
    98  			config: &config.StoredRequests{
    99  				Database: config.DatabaseConfig{
   100  					ConnectionInfo: config.DatabaseConnection{
   101  						Driver: "postgres",
   102  					},
   103  					CacheInitialization: config.DatabaseCacheInitializer{
   104  						Query: "test cache query",
   105  					},
   106  					PollUpdates: config.DatabaseUpdatePolling{
   107  						Query: "test poll query",
   108  					},
   109  					FetcherQueries: config.DatabaseFetcherQueries{
   110  						QueryTemplate: "test fetcher query",
   111  					},
   112  				},
   113  			},
   114  			emptyFetcher: false,
   115  			description:  "If Database fetcher query is defined and Database Cache init query and Database update polling query are defined not EmptyFetcher (DBFetcher) should be returned",
   116  		},
   117  	}
   118  
   119  	for _, test := range testCases {
   120  		fetcher := newFetcher(test.config, nil, db_provider.DbProviderMock{})
   121  		assert.NotNil(t, fetcher, "The fetcher should be non-nil.")
   122  		if test.emptyFetcher {
   123  			assert.Equal(t, empty_fetcher.EmptyFetcher{}, fetcher, "Empty fetcher should be returned")
   124  		} else {
   125  			assert.NotEqual(t, empty_fetcher.EmptyFetcher{}, fetcher)
   126  		}
   127  	}
   128  }
   129  
   130  func TestNewHTTPFetcher(t *testing.T) {
   131  	fetcher := newFetcher(&config.StoredRequests{
   132  		HTTP: config.HTTPFetcherConfig{
   133  			Endpoint: "stored-requests.prebid.com",
   134  		},
   135  	}, nil, nil)
   136  	if httpFetcher, ok := fetcher.(*http_fetcher.HttpFetcher); ok {
   137  		if httpFetcher.Endpoint != "stored-requests.prebid.com?" {
   138  			t.Errorf("The HTTP fetcher is using the wrong endpoint. Expected %s, got %s", "stored-requests.prebid.com?", httpFetcher.Endpoint)
   139  		}
   140  	} else {
   141  		t.Errorf("An HTTP Fetching config should return an HTTPFetcher. Got %v", fetcher)
   142  	}
   143  }
   144  
   145  func TestNewHTTPEvents(t *testing.T) {
   146  	handler := func(w http.ResponseWriter, r *http.Request) {
   147  		w.WriteHeader(http.StatusInternalServerError)
   148  	}
   149  	server1 := httptest.NewServer(http.HandlerFunc(handler))
   150  
   151  	cfg := &config.StoredRequests{
   152  		HTTPEvents: config.HTTPEventsConfig{
   153  			Endpoint:    server1.URL,
   154  			RefreshRate: 100,
   155  			Timeout:     1000,
   156  		},
   157  	}
   158  
   159  	metricsMock := &metrics.MetricsEngineMock{}
   160  
   161  	evProducers := newEventProducers(cfg, server1.Client(), nil, metricsMock, nil)
   162  	assertSliceLength(t, evProducers, 1)
   163  	assertHttpWithURL(t, evProducers[0], server1.URL)
   164  }
   165  
   166  func TestNewEmptyCache(t *testing.T) {
   167  	cache := newCache(&config.StoredRequests{InMemoryCache: config.InMemoryCache{Type: "none"}})
   168  	assert.True(t, isEmptyCacheType(cache.Requests), "The newCache method should return an empty Request cache")
   169  	assert.True(t, isEmptyCacheType(cache.Imps), "The newCache method should return an empty Imp cache")
   170  	assert.True(t, isEmptyCacheType(cache.Responses), "The newCache method should return an empty Responses cache")
   171  	assert.True(t, isEmptyCacheType(cache.Accounts), "The newCache method should return an empty Account cache")
   172  }
   173  
   174  func TestNewInMemoryCache(t *testing.T) {
   175  	cache := newCache(&config.StoredRequests{
   176  		InMemoryCache: config.InMemoryCache{
   177  			TTL:              60,
   178  			RequestCacheSize: 100,
   179  			ImpCacheSize:     100,
   180  			RespCacheSize:    100,
   181  		},
   182  	})
   183  	assert.True(t, isMemoryCacheType(cache.Requests), "The newCache method should return an in-memory Request cache for StoredRequests config")
   184  	assert.True(t, isMemoryCacheType(cache.Imps), "The newCache method should return an in-memory Imp cache for StoredRequests config")
   185  	assert.True(t, isMemoryCacheType(cache.Responses), "The newCache method should return an in-memory Responses cache for StoredResponses config")
   186  	assert.True(t, isEmptyCacheType(cache.Accounts), "The newCache method should return an empty Account cache for StoredRequests config")
   187  }
   188  
   189  func TestNewInMemoryAccountCache(t *testing.T) {
   190  	cache := newCache(typedConfig(config.AccountDataType, &config.StoredRequests{
   191  		InMemoryCache: config.InMemoryCache{
   192  			TTL:  60,
   193  			Size: 100,
   194  		},
   195  	}))
   196  	assert.True(t, isMemoryCacheType(cache.Accounts), "The newCache method should return an in-memory Account cache for Accounts config")
   197  	assert.True(t, isEmptyCacheType(cache.Requests), "The newCache method should return an empty Request cache for Accounts config")
   198  	assert.True(t, isEmptyCacheType(cache.Imps), "The newCache method should return an empty Imp cache for Accounts config")
   199  	assert.True(t, isEmptyCacheType(cache.Responses), "The newCache method should return an empty Responses cache for Accounts config")
   200  }
   201  
   202  func TestNewDatabaseEventProducers(t *testing.T) {
   203  	metricsMock := &metrics.MetricsEngineMock{}
   204  	metricsMock.Mock.On("RecordStoredDataFetchTime", mock.Anything, mock.Anything).Return()
   205  	metricsMock.Mock.On("RecordStoredDataError", mock.Anything).Return()
   206  
   207  	cfg := &config.StoredRequests{
   208  		Database: config.DatabaseConfig{
   209  			CacheInitialization: config.DatabaseCacheInitializer{
   210  				Timeout: 50,
   211  				Query:   "SELECT id, requestData, type FROM stored_data",
   212  			},
   213  			PollUpdates: config.DatabaseUpdatePolling{
   214  				RefreshRate: 20,
   215  				Timeout:     50,
   216  				Query:       "SELECT id, requestData, type FROM stored_data WHERE last_updated > $1",
   217  			},
   218  		},
   219  	}
   220  	client := &http.Client{}
   221  	provider, mock, err := db_provider.NewDbProviderMock()
   222  	if err != nil {
   223  		t.Fatalf("Failed to create mock: %v", err)
   224  	}
   225  	mock.ExpectQuery("^" + regexp.QuoteMeta(cfg.Database.CacheInitialization.Query) + "$").WillReturnError(errors.New("Query failed"))
   226  
   227  	evProducers := newEventProducers(cfg, client, provider, metricsMock, nil)
   228  	assertProducerLength(t, evProducers, 1)
   229  
   230  	assertExpectationsMet(t, mock)
   231  	metricsMock.AssertExpectations(t)
   232  }
   233  
   234  func TestNewEventsAPI(t *testing.T) {
   235  	router := httprouter.New()
   236  	newEventsAPI(router, "/test-endpoint")
   237  	if handle, _, _ := router.Lookup("POST", "/test-endpoint"); handle == nil {
   238  		t.Error("The newEventsAPI method didn't add a POST /test-endpoint route")
   239  	}
   240  	if handle, _, _ := router.Lookup("DELETE", "/test-endpoint"); handle == nil {
   241  		t.Error("The newEventsAPI method didn't add a DELETE /test-endpoint route")
   242  	}
   243  }
   244  
   245  func assertProducerLength(t *testing.T, producers []events.EventProducer, expectedLength int) {
   246  	t.Helper()
   247  	if len(producers) != expectedLength {
   248  		t.Errorf("Expected %d producers, but got %d", expectedLength, len(producers))
   249  	}
   250  }
   251  
   252  func assertExpectationsMet(t *testing.T, mock sqlmock.Sqlmock) {
   253  	if err := mock.ExpectationsWereMet(); err != nil {
   254  		t.Errorf("sqlmock expectations were not met: %v", err)
   255  	}
   256  }
   257  
   258  func assertHttpWithURL(t *testing.T, ev events.EventProducer, url string) {
   259  	if casted, ok := ev.(*httpEvents.HTTPEvents); ok {
   260  		assertStringsEqual(t, casted.Endpoint, url)
   261  	} else {
   262  		t.Errorf("The EventProducer was not a *HTTPEvents")
   263  	}
   264  }
   265  
   266  func assertSliceLength(t *testing.T, producers []events.EventProducer, expected int) {
   267  	t.Helper()
   268  
   269  	if len(producers) != expected {
   270  		t.Fatalf("Expected %d EventProducers. Got: %v", expected, producers)
   271  	}
   272  }
   273  
   274  func assertStringsEqual(t *testing.T, actual string, expected string) {
   275  	t.Helper()
   276  
   277  	if actual != expected {
   278  		t.Fatalf("String %s did not match expected %s", actual, expected)
   279  	}
   280  }