Skip to content

Commit 0f2dfce

Browse files
committed
merge devel into Pull #44
2 parents 9375b0a + 188a32c commit 0f2dfce

File tree

18 files changed

+574
-45
lines changed

18 files changed

+574
-45
lines changed

OptimizelySDKCore/OptimizelySDKCore/OPTLYLoggerMessages.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ extern NSString *const OPTLYLoggerMessagesDataStoreDatabaseGetNumberEventsTVOSWa
122122
extern NSString *const OPTLYLoggerMessagesDataStoreDatabaseRemoveTVOSWarning;
123123

124124
// ---- User Profile ----
125+
// warning
126+
extern NSString *const OPTLYLoggerMessagesUserProfileVariationNoLongerInDatafile;
125127
// Debug
126128
extern NSString *const OPTLYLoggerMessagesUserProfileVariation;
127129
extern NSString *const OPTLYLoggerMessagesUserProfileNoVariation;

OptimizelySDKCore/OptimizelySDKCore/OPTLYLoggerMessages.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@
122122
NSString *const OPTLYLoggerMessagesDataStoreDatabaseRemoveTVOSWarning = @"[DATABASE] tvOS can only remove data from cache> Number of events: %ld, eventType: %ld.";
123123

124124
// ---- User Profile ----
125+
//warning
126+
NSString *const OPTLYLoggerMessagesUserProfileVariationNoLongerInDatafile = @"Variation %@ for experiment %@ no longer found in datafile.";
125127
// Debug
126128
NSString *const OPTLYLoggerMessagesUserProfileVariation = @"Variation %@ for user %@, experiment %@ found.";
127129
NSString *const OPTLYLoggerMessagesUserProfileNoVariation = @"Variation for user %@, experiment %@ not found.";

OptimizelySDKCore/OptimizelySDKCore/Optimizely.m

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#import "OPTLYExperiment.h"
2929
#import "OPTLYLogger.h"
3030
#import "OPTLYProjectConfig.h"
31+
#import "OPTLYUserProfile.h"
3132
#import "OPTLYValidator.h"
3233
#import "OPTLYVariable.h"
3334
#import "OPTLYVariation.h"
@@ -140,11 +141,29 @@ - (OPTLYVariation *)getVariationForExperiment:(NSString *)experimentKey
140141
userId:(NSString *)userId
141142
attributes:(NSDictionary<NSString *,NSString *> *)attributes
142143
{
144+
if (self.userProfile != nil) {
145+
NSString *storedVariationKey = [self.userProfile getVariationForUser:userId experiment:experimentKey];
146+
if (storedVariationKey != nil) {
147+
OPTLYVariation *storedVariation = [[self.config getExperimentForKey:experimentKey]
148+
getVariationForVariationKey:storedVariationKey];
149+
if (storedVariation != nil) {
150+
return storedVariation;
151+
}
152+
else { // stored variation is no longer in datafile
153+
[self.userProfile removeUser:userId experiment:experimentKey];
154+
[self.logger logMessage:[NSString stringWithFormat:OPTLYLoggerMessagesUserProfileVariationNoLongerInDatafile, storedVariationKey, experimentKey]
155+
withLevel:OptimizelyLogLevelWarning];
156+
}
157+
}
158+
}
143159
OPTLYVariation *bucketedVariation = nil;
144160
bucketedVariation = [self.config getVariationForExperiment:experimentKey
145161
userId:userId
146162
attributes:attributes
147163
bucketer:self.bucketer];
164+
[self.userProfile saveUser:userId
165+
experiment:experimentKey
166+
variation:bucketedVariation.variationKey];
148167
return bucketedVariation;
149168
}
150169

OptimizelySDKDatafileManager/OptimizelySDKDatafileManager/OPTLYDatafileManager.m

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ - (void)downloadDatafile:(NSString *)projectId completionHandler:(OPTLYHTTPReque
9292
else if (statusCode == 304) {
9393
logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesDatafileManagerDatafileNotDownloadedNoChanges, projectId];
9494
[self.logger logMessage:logMessage withLevel:OptimizelyLogLevelDebug];
95+
data = [self getSavedDatafile];
9596
}
9697
else {
9798
// TODO: Josh W. handle bad response
@@ -118,7 +119,20 @@ - (void)saveDatafile:(NSData *)datafile {
118119
data:datafile
119120
type:OPTLYDataStoreDataTypeDatafile
120121
error:&error];
121-
122+
if (error != nil) {
123+
[self.errorHandler handleError:error];
124+
}
125+
}
126+
127+
- (NSData *)getSavedDatafile {
128+
NSError *error;
129+
NSData *datafile = [self.dataStore getFile:self.projectId
130+
type:OPTLYDataStoreDataTypeDatafile
131+
error:&error];
132+
if (error != nil) {
133+
[self.errorHandler handleError:error];
134+
}
135+
return datafile;
122136
}
123137

124138
- (BOOL)isDatafileCached {

OptimizelySDKDatafileManager/OptimizelySDKDatafileManagerTests/OPTLYDatafileManagerTest.m

Lines changed: 86 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
static NSString *const kDatamodelDatafileName = @"datafile_6372300739";
2626
static NSTimeInterval kDatafileDownloadInteval = 5; // in seconds
2727
static NSString *const kLastModifiedDate = @"Mon, 28 Nov 2016 06:10:59 GMT";
28+
static NSData *kDatafileData;
29+
static NSDictionary *kCDNResponseHeaders = nil;
2830

2931
@interface OPTLYDatafileManager(test)
3032
@property (nonatomic, strong) NSTimer *datafileDownloadTimer;
@@ -43,6 +45,10 @@ @implementation OPTLYDatafileManagerTest
4345
+ (void)setUp {
4446
[super setUp];
4547

48+
kCDNResponseHeaders = @{@"Content-Type":@"application/json",
49+
@"Last-Modified":kLastModifiedDate};
50+
kDatafileData = [OPTLYTestHelper loadJSONDatafileIntoDataObject:kDatamodelDatafileName];
51+
4652
// stub all requests
4753
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) {
4854
// every requests passes this test
@@ -82,7 +88,7 @@ - (void)testRequestDatafileHandlesCompletionEvenWithBadRequest {
8288
XCTAssertNotNil(self.datafileManager);
8389

8490
// stub network call
85-
[self stubResponse:400];
91+
id<OHHTTPStubsDescriptor> stub = [self stub400Response];
8692

8793
// setup async expectation
8894
__block Boolean completionWasCalled = false;
@@ -100,17 +106,16 @@ - (void)testRequestDatafileHandlesCompletionEvenWithBadRequest {
100106
[self waitForExpectationsWithTimeout:2 handler:nil];
101107
XCTAssertTrue(completionWasCalled);
102108

109+
// clean up stub
110+
[OHHTTPStubs removeStub:stub];
103111
}
104112

105113
- (void)testSaveDatafileMethod {
106114
XCTAssertNotNil(self.datafileManager);
107115
XCTAssertFalse([self.dataStore fileExists:kProjectId type:OPTLYDataStoreDataTypeDatafile]);
108116

109-
// get the datafile
110-
NSData *datafile = [OPTLYTestHelper loadJSONDatafileIntoDataObject:kDatamodelDatafileName];
111-
112117
// save the datafile
113-
[self.datafileManager saveDatafile:datafile];
118+
[self.datafileManager saveDatafile:kDatafileData];
114119

115120
// test the datafile was saved correctly
116121
bool fileExists = [self.dataStore fileExists:kProjectId type:OPTLYDataStoreDataTypeDatafile];
@@ -121,8 +126,8 @@ - (void)testSaveDatafileMethod {
121126
error:&error];
122127
XCTAssertNil(error);
123128
XCTAssertNotNil(savedData);
124-
XCTAssertNotEqual(datafile, savedData, @"we should not be referencing the same object. Saved data should be a new NSData object created from disk.");
125-
XCTAssertEqualObjects(datafile, savedData, @"retrieved saved data from disk should be equivalent to the datafile we wanted to save to disk");
129+
XCTAssertNotEqual(kDatafileData, savedData, @"we should not be referencing the same object. Saved data should be a new NSData object created from disk.");
130+
XCTAssertEqualObjects(kDatafileData, savedData, @"retrieved saved data from disk should be equivalent to the datafile we wanted to save to disk");
126131
}
127132

128133
// if 200 response, save the {projectID : lastModifiedDate} and datafile
@@ -133,7 +138,7 @@ - (void)testDatafileManagerDownloadDatafileSavesDatafile {
133138

134139
// setup stubbing and listener expectation
135140
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"testInitializeClientAsync"];
136-
[self stubResponse:200];
141+
id<OHHTTPStubsDescriptor> stub = [self stub200Response];
137142

138143
// Call download datafile
139144
[self.datafileManager downloadDatafile:self.datafileManager.projectId
@@ -145,7 +150,10 @@ - (void)testDatafileManagerDownloadDatafileSavesDatafile {
145150
}];
146151

147152
// make sure we were able to save the datafile
148-
[self waitForExpectationsWithTimeout:2 handler:nil];
153+
[self waitForExpectationsWithTimeout:2 handler:nil];
154+
155+
// clean up stub
156+
[OHHTTPStubs removeStub:stub];
149157
}
150158

151159
// timer is enabled if the download interval is > 0
@@ -188,42 +196,95 @@ - (void)testIsDatafileCachedFlag
188196
{
189197
XCTAssertFalse(self.datafileManager.isDatafileCached, @"Datafile cached flag should be false.");
190198

191-
// get the datafile
192-
NSData *datafile = [OPTLYTestHelper loadJSONDatafileIntoDataObject:kDatamodelDatafileName];
193-
194199
// save the datafile
195-
[self.datafileManager saveDatafile:datafile];
200+
[self.datafileManager saveDatafile:kDatafileData];
196201

197202
XCTAssertTrue(self.datafileManager.isDatafileCached, @"Datafile cached flag should be true.");
198203
}
199204

200205
// if 304 response datafile and last modified date should not have been saved
201206
- (void)test304Response
202207
{
203-
[self stubResponse:304];
204-
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"downloadDatafile304Response"];
208+
// stub response
209+
id<OHHTTPStubsDescriptor> stub = [self stub304Response];
210+
211+
// make sure we get a 200 the first time around and save that datafile
212+
__weak XCTestExpectation *expect200 = [self expectationWithDescription:@"should get a 200 on first try"];
213+
XCTAssertFalse([self.datafileManager isDatafileCached]);
214+
[self.datafileManager downloadDatafile:kProjectId
215+
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
216+
XCTAssertEqual(((NSHTTPURLResponse *)response).statusCode , 200);
217+
XCTAssertTrue([self.datafileManager isDatafileCached]);
218+
[expect200 fulfill];
219+
}];
220+
// wait for datafile download to finish
221+
[self waitForExpectationsWithTimeout:2 handler:nil];
222+
223+
224+
__weak XCTestExpectation *expect304 = [self expectationWithDescription:@"downloadDatafile304Response"];
225+
XCTAssertTrue([self.dataStore fileExists:kProjectId type:OPTLYDataStoreDataTypeDatafile]);
226+
XCTAssertNotNil([self.datafileManager getLastModifiedDate:kProjectId]);
205227
[self.datafileManager downloadDatafile:kProjectId completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
206-
XCTAssertFalse([self.dataStore fileExists:kProjectId type:OPTLYDataStoreDataTypeDatafile], @"Datafile should not have been saved.");
207-
NSString *savedLastModifiedData = [self.datafileManager getLastModifiedDate:kProjectId];
208-
XCTAssertNil(savedLastModifiedData, @"No modified date should have been saved.");
209-
[expectation fulfill];
228+
XCTAssertEqual(((NSHTTPURLResponse *)response).statusCode, 304);
229+
XCTAssertNotNil(data);
230+
XCTAssertEqualObjects(data, kDatafileData);
231+
[expect304 fulfill];
210232
}];
211233

212234
[self waitForExpectationsWithTimeout:2 handler:nil];
235+
236+
// remove stub
237+
[OHHTTPStubs removeStub:stub];
213238
}
214239

215240
# pragma mark - Helper Methods
216-
- (void)stubResponse:(int)statusCode {
241+
- (id<OHHTTPStubsDescriptor>)stub200Response {
217242
NSURL *hostURL = [NSURL URLWithString:OPTLYNetworkServiceCDNServerURL];
218243
NSString *hostName = [hostURL host];
219244

220-
[OHHTTPStubs stubRequestsPassingTest:^BOOL (NSURLRequest *request) {
245+
return [OHHTTPStubs stubRequestsPassingTest:^BOOL (NSURLRequest *request) {
221246
return [request.URL.host isEqualToString:hostName];
222247
} withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) {
223-
return [OHHTTPStubsResponse responseWithData:[OPTLYTestHelper loadJSONDatafileIntoDataObject:kDatamodelDatafileName]
224-
statusCode:statusCode
225-
headers:@{@"Content-Type":@"application/json",
226-
@"Last-Modified":kLastModifiedDate}];
248+
return [OHHTTPStubsResponse responseWithData:kDatafileData
249+
statusCode:200
250+
headers:kCDNResponseHeaders];
227251
}];
228252
}
253+
254+
// 304 returns nil data
255+
- (id<OHHTTPStubsDescriptor>)stub304Response {
256+
NSURL *hostURL = [NSURL URLWithString:OPTLYNetworkServiceCDNServerURL];
257+
NSString *hostName = [hostURL host];
258+
259+
return [OHHTTPStubs stubRequestsPassingTest:^BOOL (NSURLRequest *request) {
260+
return [request.URL.host isEqualToString:hostName];
261+
} withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) {
262+
if ([request.allHTTPHeaderFields objectForKey:@"If-Modified-Since"] != nil) {
263+
return [OHHTTPStubsResponse responseWithData:nil
264+
statusCode:304
265+
headers:kCDNResponseHeaders];
266+
}
267+
else {
268+
return [OHHTTPStubsResponse responseWithData:kDatafileData
269+
statusCode:200
270+
headers:kCDNResponseHeaders];
271+
272+
}
273+
}];
274+
}
275+
276+
// 400 returns nil data
277+
- (id<OHHTTPStubsDescriptor>)stub400Response {
278+
NSURL *hostURL = [NSURL URLWithString:OPTLYNetworkServiceCDNServerURL];
279+
NSString *hostName = [hostURL host];
280+
281+
return [OHHTTPStubs stubRequestsPassingTest:^BOOL (NSURLRequest *request) {
282+
return [request.URL.host isEqualToString:hostName];
283+
} withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) {
284+
return [OHHTTPStubsResponse responseWithData:nil
285+
statusCode:400
286+
headers:kCDNResponseHeaders];
287+
}];
288+
}
289+
229290
@end

OptimizelySDKShared/OptimizelySDKShared/OPTLYClientBuilder.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#import <Foundation/Foundation.h>
1818

1919
@class Optimizely, OPTLYProjectConfig, OPTLYBucketer, OPTLYEventBuilder, OPTLYEventBuilderDefault;
20-
@protocol OPTLYErrorHandler, OPTLYEventBuilder, OPTLYEventDispatcher, OPTLYLogger;
20+
@protocol OPTLYErrorHandler, OPTLYEventBuilder, OPTLYEventDispatcher, OPTLYLogger, OPTLYUserProfile;
2121

2222
/**
2323
* This class contains the informaation on how your Optimizely Client instance will be built.
@@ -39,6 +39,8 @@ typedef void (^OPTLYClientBuilderBlock)(OPTLYClientBuilder * _Nonnull builder);
3939
@property (nonatomic, readwrite, strong, nullable) id<OPTLYEventDispatcher> eventDispatcher;
4040
/// The logger is by default set to one that is created by Optimizely. This default logger can be overridden by any object that conforms to the OPTLYLogger protocol.
4141
@property (nonatomic, readwrite, strong, nullable) id<OPTLYLogger> logger;
42+
/// User profile to be used by the Optimizely instance to store user-specific data.
43+
@property (nonatomic, readwrite, strong, nullable) id<OPTLYUserProfile> userProfile;
4244

4345
/// Create an Optimizely Client object.
4446
+ (nonnull instancetype)builderWithBlock:(nonnull OPTLYClientBuilderBlock)block;

OptimizelySDKShared/OptimizelySDKShared/OPTLYClientBuilder.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ - (id)initWithBlock:(OPTLYClientBuilderBlock)block {
3737
builder.errorHandler = _errorHandler;
3838
builder.eventDispatcher = _eventDispatcher;
3939
builder.logger = _logger;
40+
builder.userProfile = _userProfile;
4041
}];
4142
_logger = _optimizely.logger;
4243
if (!_logger) {

OptimizelySDKShared/OptimizelySDKShared/OPTLYManager.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
@property (nonatomic, readonly, strong, nullable) id<OPTLYEventDispatcher> eventDispatcher;
3434
/// The logger to be used for the manager, client, and all subcomponents
3535
@property (nonatomic, readonly, strong, nullable) id<OPTLYLogger> logger;
36+
/// User profile to be used by the client to store user-specific data.
37+
@property (nonatomic, readonly, strong, nullable) id<OPTLYUserProfile> userProfile;
3638

3739
/**
3840
* Init with builder block

OptimizelySDKShared/OptimizelySDKShared/OPTLYManager.m

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,13 @@ - (instancetype)initWithBuilder:(OPTLYManagerBuilder *)builder {
4747
withLevel:OptimizelyLogLevelError];
4848
return nil;
4949
}
50-
_projectId = builder.projectId;
5150
_datafile = builder.datafile;
51+
_datafileManager = builder.datafileManager;
5252
_errorHandler = builder.errorHandler;
5353
_eventDispatcher = builder.eventDispatcher;
5454
_logger = builder.logger;
55-
// initialize datafile manager
56-
_datafileManager = builder.datafileManager;
57-
// TODO: Josh W. initialize event dispatcher
58-
// TODO: Josh W. initialize user experiment record
55+
_projectId = builder.projectId;
56+
_userProfile = builder.userProfile;
5957
}
6058
return self;
6159
}
@@ -80,19 +78,15 @@ - (instancetype)initWithBuilder:(OPTLYManagerBuilder *)builder {
8078
}
8179

8280
- (OPTLYClient *)initializeClient {
83-
OPTLYClient *client = [OPTLYClient initWithBuilderBlock:^(OPTLYClientBuilder * _Nonnull builder) {
84-
builder.datafile = self.datafile;
85-
}];
81+
OPTLYClient *client = [self initializeClientWithManagerSettingsAndDatafile:self.datafile];
8682
if (client.optimizely != nil) {
8783
self.optimizelyClient = client;
8884
}
8985
return client;
9086
}
9187

9288
- (OPTLYClient *)initializeClientWithDatafile:(NSData *)datafile {
93-
OPTLYClient *client = [OPTLYClient initWithBuilderBlock:^(OPTLYClientBuilder * _Nonnull builder) {
94-
builder.datafile = datafile;
95-
}];
89+
OPTLYClient *client = [self initializeClientWithManagerSettingsAndDatafile:datafile];
9690
if (client.optimizely != nil) {
9791
self.optimizelyClient = client;
9892
return client;
@@ -104,11 +98,8 @@ - (OPTLYClient *)initializeClientWithDatafile:(NSData *)datafile {
10498

10599
- (void)initializeClientWithCallback:(void (^)(NSError * _Nullable, OPTLYClient * _Nullable))callback {
106100
[self.datafileManager downloadDatafile:self.projectId completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
107-
OPTLYClient *client = nil;
108101
if (!error) {
109-
client = [OPTLYClient initWithBuilderBlock:^(OPTLYClientBuilder * _Nonnull builder) {
110-
builder.datafile = data;
111-
}];
102+
OPTLYClient *client = [self initializeClientWithManagerSettingsAndDatafile:data];
112103
if (client.optimizely) {
113104
self.optimizelyClient = client;
114105
}
@@ -117,7 +108,7 @@ - (void)initializeClientWithCallback:(void (^)(NSError * _Nullable, OPTLYClient
117108
}
118109

119110
if (callback) {
120-
callback(error, client);
111+
callback(error, self.optimizelyClient);
121112
}
122113
}];
123114
}
@@ -126,4 +117,15 @@ - (OPTLYClient *)getOptimizely {
126117
return self.optimizelyClient;
127118
}
128119

120+
- (OPTLYClient *)initializeClientWithManagerSettingsAndDatafile:(NSData *)datafile {
121+
OPTLYClient *client = [OPTLYClient initWithBuilderBlock:^(OPTLYClientBuilder * _Nonnull builder) {
122+
builder.datafile = datafile;
123+
builder.errorHandler = self.errorHandler;
124+
builder.eventDispatcher = self.eventDispatcher;
125+
builder.logger = self.logger;
126+
builder.userProfile = self.userProfile;
127+
}];
128+
return client;
129+
}
130+
129131
@end

0 commit comments

Comments
 (0)