diff --git a/CHANGELOG.md b/CHANGELOG.md index be8d1ec..208f7b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +### Version: 3.16.0 +#### Date: Sep-22-2025 + +##### Feature: + - Added support for AU and GCP-EU regions + ### Version: 3.15.0 #### Date: Jun-16-2025 diff --git a/Contentstack.podspec b/Contentstack.podspec index a382b2e..cebddcf 100644 --- a/Contentstack.podspec +++ b/Contentstack.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Contentstack' -s.version = '3.15.0' +s.version = '3.16.0' s.summary = 'Contentstack is a headless CMS with an API-first approach that puts content at the centre.' s.description = <<-DESC diff --git a/Contentstack/Config.m b/Contentstack/Config.m index 1dbde51..f2dc696 100755 --- a/Contentstack/Config.m +++ b/Contentstack/Config.m @@ -22,10 +22,10 @@ -(instancetype)init { } - (void)setRegion:(ContentstackRegion)region { _region = region; - if ([[self hostURLS] containsObject:_host]) { - _host = [self hostURL:_region]; - } + // Always update host when region changes and notify observers + _host = [self hostURL:_region]; // Update host based on region } + - (NSDictionary *)earlyAccessHeaders { if (_setEarlyAccess.count > 0) { NSString *earlyAccessString = [_setEarlyAccess componentsJoinedByString:@","]; diff --git a/Contentstack/ContentstackDefinitions.h b/Contentstack/ContentstackDefinitions.h index ceac7f8..0dfed20 100755 --- a/Contentstack/ContentstackDefinitions.h +++ b/Contentstack/ContentstackDefinitions.h @@ -78,9 +78,11 @@ typedef NS_ENUM(NSUInteger, ContentstackRegion){ US = 0, EU, + AU, AZURE_NA, AZURE_EU, - GCP_NA + GCP_NA, + GCP_EU }; typedef NS_ENUM(NSUInteger, Language) { diff --git a/Contentstack/Stack.h b/Contentstack/Stack.h index dbf3b23..250fed7 100644 --- a/Contentstack/Stack.h +++ b/Contentstack/Stack.h @@ -45,6 +45,12 @@ BUILT_ASSUME_NONNULL_BEGIN */ @property (nonatomic, copy, readonly) Config *config; +/** + * Readonly property to get the current host URL for the stack. + * This value is derived from the config's host and updates automatically when the region changes. + */ +@property (nonatomic, copy, readonly) NSString *hostURL; + - (instancetype)init UNAVAILABLE_ATTRIBUTE; //MARK: - ContentType diff --git a/Contentstack/Stack.m b/Contentstack/Stack.m index 42faff2..8fa06d7 100644 --- a/Contentstack/Stack.m +++ b/Contentstack/Stack.m @@ -20,21 +20,19 @@ @interface Stack () @property (nonatomic, copy) NSString *apiKey; @property (nonatomic, copy) NSString *accessToken; @property (nonatomic, copy) Config *config; - @property (nonatomic, copy) NSString *environment; @end @implementation Stack +- (NSString *)hostURL { + return self.config.host; +} + - (instancetype)initWithAPIKey:(NSString*)apiKey andaccessToken:(NSString *)accessToken andEnvironment:(NSString*)environment andConfig:(Config *)sConfig { if (self = [super init]) { _config = sConfig; - - _hostURL = [sConfig.host copy]; - if (_config.region != US) { - _hostURL = [NSString stringWithFormat:@"%@-%@", [self regionCode:_config.region], sConfig.host]; - } _version = [sConfig.version copy]; _environment = [environment copy]; @@ -49,19 +47,18 @@ - (instancetype)initWithAPIKey:(NSString*)apiKey andaccessToken:(NSString *)acce _requestOperationSet = [NSMutableSet set]; // Add early access headers only if they exist - NSDictionary *earlyAccessHeaders = [_config earlyAccessHeaders]; - if (earlyAccessHeaders.count > 0) { - [_stackHeaders addEntriesFromDictionary:earlyAccessHeaders]; - } + NSDictionary *earlyAccessHeaders = [_config earlyAccessHeaders]; + if (earlyAccessHeaders.count > 0) { + [_stackHeaders addEntriesFromDictionary:earlyAccessHeaders]; + } - [self setHeader:_apiKey forKey:kCSIO_SiteApiKey]; [self setHeader:_accessToken forKey:kCSIO_Accesstoken]; - } return self; } + //MARK: - Get ContentTypes -(void)getContentTypes:(NSDictionary *)params completion:(void (^)(NSArray * _Nullable, NSError * _Nullable))completionBlock { NSString *path = [CSIOAPIURLs fetchSchemaWithVersion:self.version]; diff --git a/ContentstackInternal/NSObject+Extensions.m b/ContentstackInternal/NSObject+Extensions.m index 23954f4..2d7f10d 100755 --- a/ContentstackInternal/NSObject+Extensions.m +++ b/ContentstackInternal/NSObject+Extensions.m @@ -42,11 +42,13 @@ -(NSArray*)hostURLS { static NSArray* hostURLS; static dispatch_once_t hostURLSOnceToken; dispatch_once(&hostURLSOnceToken, ^{ - hostURLS = @[@"cdn.contentstack.io", - @"cdn.contentstack.com", - @"cdn.contentstack.com", - @"cdn.contentstack.com", - @"cdn.contentstack.com"]; + hostURLS = @[@"cdn.contentstack.io", // US + @"eu-cdn.contentstack.com", // EU + @"au-cdn.contentstack.com", // AU + @"azure-na-cdn.contentstack.com", // Azure NA + @"azure-eu-cdn.contentstack.com", // Azure EU + @"gcp-na-cdn.contentstack.com", // GCP NA + @"gcp-eu-cdn.contentstack.com"]; // GCP EU }); return hostURLS; } @@ -61,9 +63,12 @@ -(NSArray*)regionCodes { dispatch_once(®ionCodesOnceToken, ^{ regionCodes = @[@"us", @"eu", + @"au", @"azure-na", @"azure-eu", - @"gcp-na"]; + @"gcp-na", + @"gcp-eu" + ]; }); return regionCodes; } diff --git a/ContentstackTest/ContentstackTest.m b/ContentstackTest/ContentstackTest.m index 7214b2d..361cea9 100644 --- a/ContentstackTest/ContentstackTest.m +++ b/ContentstackTest/ContentstackTest.m @@ -1067,7 +1067,7 @@ - (void)testFetchEntryEqualToField { ContentType* csForm = [csStack contentTypeWithName:@"source"]; Query* csQuery = [csForm query]; - __block NSString *objectValue = @"source"; + __block NSString *objectValue = @"source1"; [csQuery whereKey:@"title" equalTo:objectValue]; [csQuery find:^(ResponseType type, QueryResult *result, NSError *error) { @@ -2013,7 +2013,7 @@ - (void)testFetchTags { ContentType* csForm = [csStack contentTypeWithName:@"source"]; Query* csQuery = [csForm query]; - __block NSMutableArray *tags = [NSMutableArray arrayWithArray:@[@"tags1",@"tags2"]]; + __block NSMutableArray *tags = [NSMutableArray arrayWithArray:@[@"tag1",@"tag2"]]; [csQuery tags:tags]; [csQuery find:^(ResponseType type, QueryResult *result, NSError *error) { @@ -2122,7 +2122,7 @@ - (void)testFetchSkipEntries { ContentType* csForm = [csStack contentTypeWithName:@"source"]; - __block NSInteger skipObject = 4; + __block NSInteger skipObject = 1; Query* csQuery = [csForm query]; [csQuery includeCount]; [csQuery skipObjects:@(skipObject)]; @@ -2133,7 +2133,7 @@ - (void)testFetchSkipEntries { XCTFail(@"~ ERR: %@", error.userInfo); } else { - XCTAssertTrue(([result totalCount]-skipObject) <= [result getResult].count, "query should skip 4 objects"); + XCTAssertTrue(([result totalCount]-skipObject) <= [result getResult].count, "query should skip 1 objects"); } [expectation fulfill]; @@ -2545,4 +2545,96 @@ - (void)testVariantHeaders { }]; } +#pragma mark - Region Support Tests + +- (void)testDefaultRegion { + XCTestExpectation *expectation = [self expectationWithDescription:@"DefaultRegionTest"]; + config = [[Config alloc] init]; + Stack *stack = [Contentstack stackWithAPIKey:@"api_key" accessToken:@"delivery_token" environmentName:@"environment" config:config]; + + // Verify default region is US + XCTAssertEqual(config.region, US, @"Default region should be US"); + XCTAssertEqualObjects(config.host, @"cdn.contentstack.io", @"Config host should be US region"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:kRequestTimeOutInSeconds handler:nil]; +} + +- (void)testAllRegionsSupport { + XCTestExpectation *expectation = [self expectationWithDescription:@"AllRegionsTest"]; + config = [[Config alloc] init]; + Stack *stack = [Contentstack stackWithAPIKey:@"api_key" accessToken:@"delivery_token" environmentName:@"environment" config:config]; + + // Test all regions + NSDictionary *regionHosts = @{ + @(US): @"cdn.contentstack.io", + @(EU): @"eu-cdn.contentstack.com", + @(AU): @"au-cdn.contentstack.com", + @(AZURE_NA): @"azure-na-cdn.contentstack.com", + @(AZURE_EU): @"azure-eu-cdn.contentstack.com", + @(GCP_NA): @"gcp-na-cdn.contentstack.com", + @(GCP_EU): @"gcp-eu-cdn.contentstack.com" + }; + + [regionHosts enumerateKeysAndObjectsUsingBlock:^(NSNumber *region, NSString *expectedHost, BOOL *stop) { + [config setRegion:region.intValue]; + XCTAssertEqualObjects(config.host, expectedHost, + @"Config host should be updated for region %@", region); + }]; + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:kRequestTimeOutInSeconds handler:nil]; +} + +- (void)testCustomHostOverride { + XCTestExpectation *expectation = [self expectationWithDescription:@"CustomHostTest"]; + config = [[Config alloc] init]; + Stack *stack = [Contentstack stackWithAPIKey:@"api_key" accessToken:@"delivery_token" environmentName:@"environment" config:config]; + + // Set custom host + NSString *customHost = @"custom.contentstack.com"; + config.host = customHost; + XCTAssertEqualObjects(config.host, customHost, @"Config should use custom host"); + + // Verify region change overrides custom host + [config setRegion:AU]; + XCTAssertEqualObjects(config.host, @"au-cdn.contentstack.com", @"Region change should override custom host"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:kRequestTimeOutInSeconds handler:nil]; +} + +- (void)testRegionChangeReflection { + XCTestExpectation *expectation = [self expectationWithDescription:@"RegionChangeTest"]; + config = [[Config alloc] init]; + Stack *stack = [Contentstack stackWithAPIKey:@"api_key" accessToken:@"delivery_token" environmentName:@"environment" config:config]; + + // Change regions multiple times + [config setRegion:EU]; + XCTAssertEqualObjects(config.host, @"eu-cdn.contentstack.com", @"Config should update to EU host"); + + [config setRegion:AU]; + XCTAssertEqualObjects(config.host, @"au-cdn.contentstack.com", @"Config should update to AU host"); + + [config setRegion:US]; + XCTAssertEqualObjects(config.host, @"cdn.contentstack.io", @"Config should update back to US host"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:kRequestTimeOutInSeconds handler:nil]; +} + +- (void)testAURegion { + XCTestExpectation *expectation = [self expectationWithDescription:@"DefaultRegionTest"]; + config = [[Config alloc] init]; + [config setRegion:AU]; + Stack *stack = [Contentstack stackWithAPIKey:@"api_key" accessToken:@"delivery_token" environmentName:@"environment" config:config]; + + // Verify default region is US + XCTAssertEqual(config.region, AU, @"Default region should be AU"); + XCTAssertEqualObjects(config.host, @"au-cdn.contentstack.com", @"Config host should be AU region"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:kRequestTimeOutInSeconds handler:nil]; +} + @end