From f53c0ae985e90d414f8aedfde594891665ec84d0 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 14 Nov 2025 12:54:53 +0530 Subject: [PATCH 1/2] Refactor asset retrieval logic in Entry.m for improved readability and efficiency. Update project scheme version and enable code coverage settings. Enhance JSON handling in NSObject+Extensions.m with nil checks and type validation. Add multiple test files to project configuration. --- Contentstack.xcodeproj/project.pbxproj | 52 ++ .../xcschemes/Contentstack.xcscheme | 19 +- Contentstack/Entry.m | 15 +- ContentstackInternal/NSObject+Extensions.m | 20 +- ContentstackTest/AssetEdgeCaseTest.m | 334 +++++++++++ ContentstackTest/AssetLibraryTest.m | 564 ++++++++++++++++++ ContentstackTest/ContentTypeEdgeCaseTest.m | 196 ++++++ ContentstackTest/ContentstackMainTest.m | 164 +++++ ContentstackTest/EntryAdvancedTest.m | 264 ++++++++ ContentstackTest/EntryEdgeCaseTest.m | 481 +++++++++++++++ ContentstackTest/GroupTest.m | 448 ++++++++++++++ ContentstackTest/NSObjectExtensionsTest.m | 336 +++++++++++ ContentstackTest/QueryAdvancedTest.m | 361 +++++++++++ ContentstackTest/QueryEdgeCaseTest.m | 425 +++++++++++++ ContentstackTest/QueryResultAdvancedTest.m | 242 ++++++++ ContentstackTest/QueryResultTest.m | 208 +++++++ ContentstackTest/TaxonomyTest.m | 330 ++++++++++ 17 files changed, 4443 insertions(+), 16 deletions(-) create mode 100644 ContentstackTest/AssetEdgeCaseTest.m create mode 100644 ContentstackTest/AssetLibraryTest.m create mode 100644 ContentstackTest/ContentTypeEdgeCaseTest.m create mode 100644 ContentstackTest/ContentstackMainTest.m create mode 100644 ContentstackTest/EntryAdvancedTest.m create mode 100644 ContentstackTest/EntryEdgeCaseTest.m create mode 100644 ContentstackTest/GroupTest.m create mode 100644 ContentstackTest/NSObjectExtensionsTest.m create mode 100644 ContentstackTest/QueryAdvancedTest.m create mode 100644 ContentstackTest/QueryEdgeCaseTest.m create mode 100644 ContentstackTest/QueryResultAdvancedTest.m create mode 100644 ContentstackTest/QueryResultTest.m create mode 100644 ContentstackTest/TaxonomyTest.m diff --git a/Contentstack.xcodeproj/project.pbxproj b/Contentstack.xcodeproj/project.pbxproj index babb0d5..da65991 100644 --- a/Contentstack.xcodeproj/project.pbxproj +++ b/Contentstack.xcodeproj/project.pbxproj @@ -107,9 +107,22 @@ 64B3EA282BF7C4AF009E0F38 /* libThirdPartyExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 238E841B1B4FE29A00BFDB32 /* libThirdPartyExtension.a */; }; 64F5220E2BF5C76E00AE6E0F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 64F5220D2BF5C76E00AE6E0F /* PrivacyInfo.xcprivacy */; }; 64F5220F2BF5C76E00AE6E0F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 64F5220D2BF5C76E00AE6E0F /* PrivacyInfo.xcprivacy */; }; + 6787D39B2EBC671F00A2F637 /* NSObjectExtensionsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6787D39A2EBC671F00A2F637 /* NSObjectExtensionsTest.m */; }; + 6787D39D2EBC673500A2F637 /* EntryAdvancedTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6787D39C2EBC673500A2F637 /* EntryAdvancedTest.m */; }; + 6787D39F2EBC673600A2F637 /* QueryAdvancedTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6787D39E2EBC673600A2F637 /* QueryAdvancedTest.m */; }; 678803D22DED82A100E4AA75 /* GlobalField.h in Headers */ = {isa = PBXBuildFile; fileRef = 678803D12DED829800E4AA75 /* GlobalField.h */; }; 678803D42DED82AF00E4AA75 /* GlobalField.m in Sources */ = {isa = PBXBuildFile; fileRef = 678803D32DED82AC00E4AA75 /* GlobalField.m */; }; 678803D62DEDB24800E4AA75 /* GlobalFieldTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 678803D52DEDB23800E4AA75 /* GlobalFieldTest.m */; }; + 67AA391A2EBB354F00C0E2C0 /* TaxonomyTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 67AA39192EBB354F00C0E2C0 /* TaxonomyTest.m */; }; + 67AA391C2EBB355600C0E2C0 /* QueryResultTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 67AA391B2EBB355600C0E2C0 /* QueryResultTest.m */; }; + 67AA391E2EBB355A00C0E2C0 /* QueryEdgeCaseTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 67AA391D2EBB355A00C0E2C0 /* QueryEdgeCaseTest.m */; }; + 67AA39202EBB355F00C0E2C0 /* GroupTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 67AA391F2EBB355F00C0E2C0 /* GroupTest.m */; }; + 67AA39222EBB356400C0E2C0 /* EntryEdgeCaseTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 67AA39212EBB356400C0E2C0 /* EntryEdgeCaseTest.m */; }; + 67AA39242EBB356800C0E2C0 /* ContentTypeEdgeCaseTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 67AA39232EBB356800C0E2C0 /* ContentTypeEdgeCaseTest.m */; }; + 67AA39262EBB356D00C0E2C0 /* AssetLibraryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 67AA39252EBB356D00C0E2C0 /* AssetLibraryTest.m */; }; + 67AA39282EBB357400C0E2C0 /* AssetEdgeCaseTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 67AA39272EBB357400C0E2C0 /* AssetEdgeCaseTest.m */; }; + BFDECB7ADED46A51700FE398 /* ContentstackMainTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DD745A781E5B6171D9E68BB8 /* ContentstackMainTest.m */; }; + DA052EBF01230642692DA859 /* QueryResultAdvancedTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 55FB34FC5E57D6E11D51A686 /* QueryResultAdvancedTest.m */; }; E653FF942F28495541E9B22B /* libPods-Contentstack.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4514C6AB47DF26BA926C681A /* libPods-Contentstack.a */; }; /* End PBXBuildFile section */ @@ -226,6 +239,7 @@ 4714B7D32C5EAFCC004E941E /* Taxonomy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Taxonomy.m; sourceTree = ""; }; 4714B7D52C5EAFF5004E941E /* Taxonomy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Taxonomy.h; sourceTree = ""; }; 473AFDAF2CA22233002D331D /* config.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = config.json; sourceTree = ""; }; + 55FB34FC5E57D6E11D51A686 /* QueryResultAdvancedTest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = QueryResultAdvancedTest.m; sourceTree = ""; }; 565E11A91BD76654005AD47F /* MMDocument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMDocument.h; sourceTree = ""; }; 565E11AA1BD76654005AD47F /* MMDocument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMDocument.m; sourceTree = ""; }; 565E11AB1BD76654005AD47F /* MMDocument_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMDocument_Private.h; sourceTree = ""; }; @@ -247,13 +261,25 @@ 606DDA20A6F0593F40494FED /* Pods-ContentstackTest.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ContentstackTest.release.xcconfig"; path = "Target Support Files/Pods-ContentstackTest/Pods-ContentstackTest.release.xcconfig"; sourceTree = ""; }; 609D1D72B25D2FBE4E26FA70 /* Pods-ContentstackTest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ContentstackTest.debug.xcconfig"; path = "Target Support Files/Pods-ContentstackTest/Pods-ContentstackTest.debug.xcconfig"; sourceTree = ""; }; 64F5220D2BF5C76E00AE6E0F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 6787D39A2EBC671F00A2F637 /* NSObjectExtensionsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NSObjectExtensionsTest.m; sourceTree = ""; }; + 6787D39C2EBC673500A2F637 /* EntryAdvancedTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EntryAdvancedTest.m; sourceTree = ""; }; + 6787D39E2EBC673600A2F637 /* QueryAdvancedTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QueryAdvancedTest.m; sourceTree = ""; }; 678803D12DED829800E4AA75 /* GlobalField.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GlobalField.h; sourceTree = ""; }; 678803D32DED82AC00E4AA75 /* GlobalField.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GlobalField.m; sourceTree = ""; }; 678803D52DEDB23800E4AA75 /* GlobalFieldTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GlobalFieldTest.m; sourceTree = ""; }; + 67AA39192EBB354F00C0E2C0 /* TaxonomyTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TaxonomyTest.m; sourceTree = ""; }; + 67AA391B2EBB355600C0E2C0 /* QueryResultTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QueryResultTest.m; sourceTree = ""; }; + 67AA391D2EBB355A00C0E2C0 /* QueryEdgeCaseTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QueryEdgeCaseTest.m; sourceTree = ""; }; + 67AA391F2EBB355F00C0E2C0 /* GroupTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GroupTest.m; sourceTree = ""; }; + 67AA39212EBB356400C0E2C0 /* EntryEdgeCaseTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EntryEdgeCaseTest.m; sourceTree = ""; }; + 67AA39232EBB356800C0E2C0 /* ContentTypeEdgeCaseTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ContentTypeEdgeCaseTest.m; sourceTree = ""; }; + 67AA39252EBB356D00C0E2C0 /* AssetLibraryTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AssetLibraryTest.m; sourceTree = ""; }; + 67AA39272EBB357400C0E2C0 /* AssetEdgeCaseTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AssetEdgeCaseTest.m; sourceTree = ""; }; 7EB1C6B5FF6A451CEB50B3A4 /* libPods-ContentstackTest.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ContentstackTest.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 8B7BE798B2EEFA3CC2763E3F /* Pods-Contentstack.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Contentstack.release.xcconfig"; path = "Target Support Files/Pods-Contentstack/Pods-Contentstack.release.xcconfig"; sourceTree = ""; }; 9980728A1E1BDC5000524FD3 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; ADFEA7B22B9D9042C8508BEC /* Pods-Contentstack.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Contentstack.debug.xcconfig"; path = "Target Support Files/Pods-Contentstack/Pods-Contentstack.debug.xcconfig"; sourceTree = ""; }; + DD745A781E5B6171D9E68BB8 /* ContentstackMainTest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = ContentstackMainTest.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -439,10 +465,23 @@ isa = PBXGroup; children = ( 0F41A91025C7CC9C007EF2DA /* ContentstackTest.m */, + 67AA39192EBB354F00C0E2C0 /* TaxonomyTest.m */, + 67AA391B2EBB355600C0E2C0 /* QueryResultTest.m */, + 6787D39A2EBC671F00A2F637 /* NSObjectExtensionsTest.m */, + 67AA391D2EBB355A00C0E2C0 /* QueryEdgeCaseTest.m */, + 6787D39C2EBC673500A2F637 /* EntryAdvancedTest.m */, + 67AA39252EBB356D00C0E2C0 /* AssetLibraryTest.m */, + 67AA39272EBB357400C0E2C0 /* AssetEdgeCaseTest.m */, + 6787D39E2EBC673600A2F637 /* QueryAdvancedTest.m */, + 67AA39232EBB356800C0E2C0 /* ContentTypeEdgeCaseTest.m */, + 67AA39212EBB356400C0E2C0 /* EntryEdgeCaseTest.m */, + 67AA391F2EBB355F00C0E2C0 /* GroupTest.m */, 0F41A91125C7CC9C007EF2DA /* SyncTest.m */, 678803D52DEDB23800E4AA75 /* GlobalFieldTest.m */, 23A53F591E277CD3001DBE35 /* Info.plist */, 473AFDAF2CA22233002D331D /* config.json */, + DD745A781E5B6171D9E68BB8 /* ContentstackMainTest.m */, + 55FB34FC5E57D6E11D51A686 /* QueryResultAdvancedTest.m */, ); path = ContentstackTest; sourceTree = ""; @@ -834,9 +873,22 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 67AA391A2EBB354F00C0E2C0 /* TaxonomyTest.m in Sources */, + 67AA39222EBB356400C0E2C0 /* EntryEdgeCaseTest.m in Sources */, + 67AA39262EBB356D00C0E2C0 /* AssetLibraryTest.m in Sources */, + 6787D39B2EBC671F00A2F637 /* NSObjectExtensionsTest.m in Sources */, + 67AA391E2EBB355A00C0E2C0 /* QueryEdgeCaseTest.m in Sources */, 0F41A91425C7CC9C007EF2DA /* ContentstackTest.m in Sources */, + 6787D39F2EBC673600A2F637 /* QueryAdvancedTest.m in Sources */, + 67AA391C2EBB355600C0E2C0 /* QueryResultTest.m in Sources */, 0F41A91525C7CC9C007EF2DA /* SyncTest.m in Sources */, + 6787D39D2EBC673500A2F637 /* EntryAdvancedTest.m in Sources */, + 67AA39242EBB356800C0E2C0 /* ContentTypeEdgeCaseTest.m in Sources */, + 67AA39282EBB357400C0E2C0 /* AssetEdgeCaseTest.m in Sources */, + 67AA39202EBB355F00C0E2C0 /* GroupTest.m in Sources */, 678803D62DEDB24800E4AA75 /* GlobalFieldTest.m in Sources */, + BFDECB7ADED46A51700FE398 /* ContentstackMainTest.m in Sources */, + DA052EBF01230642692DA859 /* QueryResultAdvancedTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Contentstack.xcodeproj/xcshareddata/xcschemes/Contentstack.xcscheme b/Contentstack.xcodeproj/xcshareddata/xcschemes/Contentstack.xcscheme index 07e34ac..ffb0d7c 100644 --- a/Contentstack.xcodeproj/xcshareddata/xcschemes/Contentstack.xcscheme +++ b/Contentstack.xcodeproj/xcshareddata/xcschemes/Contentstack.xcscheme @@ -1,7 +1,7 @@ + version = "1.7"> @@ -26,7 +26,9 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES" + onlyGenerateCoverageForSpecifiedTargets = "YES"> + + + + @@ -46,6 +57,10 @@ BlueprintName = "ContentstackTest" ReferencedContainer = "container:Contentstack.xcodeproj"> + + diff --git a/Contentstack/Entry.m b/Contentstack/Entry.m index bead34f..f97f819 100644 --- a/Contentstack/Entry.m +++ b/Contentstack/Entry.m @@ -350,13 +350,14 @@ - (Asset *)assetForKey:(NSString *)key { - (NSArray *)assetsForKey:(NSString *)key { NSMutableArray *fileArray = [NSMutableArray array]; id obj = [self.objectProperties objectForKey:key]; - if (obj && [obj isKindOfClass:[NSArray class]]) { - NSArray *arr = (NSArray *)obj; - for (NSDictionary *dict in arr) { - Asset *file = [self assetFile:dict]; - if (file && ![file isKindOfClass:[NSNull class]]) { - [fileArray addObject:file]; - } + if (!(obj && [obj isKindOfClass:[NSArray class]])) { + return fileArray; + } + NSArray *arr = (NSArray *)obj; + for (NSDictionary *dict in arr) { + Asset *file = [self assetFile:dict]; + if (file && ![file isKindOfClass:[NSNull class]]) { + [fileArray addObject:file]; } } return fileArray; diff --git a/ContentstackInternal/NSObject+Extensions.m b/ContentstackInternal/NSObject+Extensions.m index 2d7f10d..599cf72 100755 --- a/ContentstackInternal/NSObject+Extensions.m +++ b/ContentstackInternal/NSObject+Extensions.m @@ -267,26 +267,32 @@ - (void)assertPropertyTypes:(NSDictionary *)properties { } - (NSDictionary *)dictionaryFromJSONData:(NSData *)data { - NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; - return dict; + if (data == nil) { return nil; } + if (data.length == 0) { return nil; } + id obj = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + return [obj isKindOfClass:[NSDictionary class]] ? obj : nil; } - (NSData *)jsonDataFromDictonary:(NSDictionary *)dict { - NSData *JSONData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:NULL]; - return JSONData; + if (dict == nil) { return nil; } + return [NSJSONSerialization dataWithJSONObject:dict options:0 error:NULL]; } - (NSString *)jsonStringFromDictonary:(NSDictionary *)dict { - NSData *JSONData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:NULL]; + NSData *JSONData = [self jsonDataFromDictonary:dict]; + if (JSONData == nil) { return nil; } return [[NSString alloc] initWithData:JSONData encoding:NSUTF8StringEncoding]; } - (NSArray *)arrayFromJSONData:(NSData *)data { - NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; - return array; + if (data == nil) { return nil; } + if (data.length == 0) { return nil; } + id obj = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + return [obj isKindOfClass:[NSArray class]] ? obj : nil; } - (NSString *)jsonStringFromArray:(NSArray*)array { + if (array == nil) { return nil; } NSData *JSONData = [NSJSONSerialization dataWithJSONObject:array options:0 error:NULL]; return [[NSString alloc] initWithData:JSONData encoding:NSUTF8StringEncoding]; } diff --git a/ContentstackTest/AssetEdgeCaseTest.m b/ContentstackTest/AssetEdgeCaseTest.m new file mode 100644 index 0000000..c384be0 --- /dev/null +++ b/ContentstackTest/AssetEdgeCaseTest.m @@ -0,0 +1,334 @@ +// +// AssetEdgeCaseTest.m +// ContentstackTest +// +// Created by Test Suite on 05/11/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import +#import +#import "CSIOInternalHeaders.h" +#import "ContentstackDefinitions.h" + +static NSInteger kRequestTimeOutInSeconds = 30; + +@interface AssetEdgeCaseTest : XCTestCase { + Stack *csStack; + Config *config; +} + +@property (nonatomic, strong) Stack *stack; + +@end + +@implementation AssetEdgeCaseTest + +- (void)setUp { + [super setUp]; + NSString *path = [[NSBundle bundleForClass:self.class] pathForResource:@"config" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:path]; + NSDictionary *configdict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; + config = [[Config alloc] init]; + config.host = configdict[@"host"]; + self.stack = [Contentstack stackWithAPIKey:configdict[@"api_key"] + accessToken:configdict[@"delivery_token"] + environmentName:configdict[@"environment"] + config:config]; +} + +- (void)waitForRequest { + [self waitForExpectationsWithTimeout:kRequestTimeOutInSeconds handler:^(NSError *error) { + if (error) { + XCTFail(@"Could not perform operation (Timed out) ~ ERR: %@", error.userInfo); + } + }]; +} + +- (void)tearDown { + self.stack = nil; + [super tearDown]; +} + +#pragma mark - Asset Header Tests + +- (void)testAssetSetHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Set Header"]; + + Asset *asset = [self.stack asset]; + [asset setHeader:@"TestValue" forKey:@"X-Test-Header"]; + XCTAssertNotNil(asset); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testAssetAddHeadersWithDictionary { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Add Headers Dictionary"]; + + Asset *asset = [self.stack asset]; + + NSDictionary *headersToAdd = @{ + @"Header-1": @"Value1", + @"Header-2": @"Value2", + @"Header-3": @"Value3" + }; + + [asset addHeadersWithDictionary:headersToAdd]; + XCTAssertNotNil(asset); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testAssetRemoveHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Remove Header"]; + + Asset *asset = [self.stack asset]; + + // Add header first + [asset setHeader:@"TestValue" forKey:@"X-Test-Header"]; + + // Remove header + [asset removeHeaderForKey:@"X-Test-Header"]; + XCTAssertNotNil(asset); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testAssetRemoveNonExistentHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Remove Non-Existent Header"]; + + Asset *asset = [self.stack asset]; + + // Try to remove non-existent header (should not crash) + [asset removeHeaderForKey:@"NonExistent"]; + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Asset Parameter Tests + +- (void)testAssetAddParamKeyValue { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Add Param"]; + + Asset *asset = [self.stack asset]; + [asset addParamKey:@"dimension" andValue:@"width=100"]; + XCTAssertNotNil(asset); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testAssetAddMultipleParams { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Add Multiple Params"]; + + Asset *asset = [self.stack asset]; + [asset addParamKey:@"width" andValue:@"100"]; + [asset addParamKey:@"height" andValue:@"200"]; + [asset addParamKey:@"format" andValue:@"png"]; + XCTAssertNotNil(asset); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Asset Include Methods + +- (void)testAssetIncludeFallback { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Include Fallback"]; + + Asset *asset = [self.stack asset]; + [asset includeFallback]; + XCTAssertNotNil(asset); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testAssetIncludeMetadata { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Include Metadata"]; + + Asset *asset = [self.stack asset]; + [asset includeMetadata]; + XCTAssertNotNil(asset); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testAssetIncludeBranch { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Include Branch"]; + + Asset *asset = [self.stack asset]; + [asset includeBranch]; + XCTAssertNotNil(asset); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Asset Fetch with Options + +- (void)testAssetFetchWithMetadata { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Fetch with Metadata"]; + + Asset *asset = [self.stack assetWithUID:@"blt240v03mgonxgwvvf"]; + [asset includeMetadata]; + + [asset fetch:^(ResponseType type, NSError * _Nullable error) { + if (error) { + // Asset UID may not exist + XCTAssertNotNil(error); + } else { + XCTAssertNil(error); + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testAssetFetchWithFallback { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Fetch with Fallback"]; + + Asset *asset = [self.stack assetWithUID:@"blt240v03mgonxgwvvf"]; + [asset includeFallback]; + + [asset fetch:^(ResponseType type, NSError * _Nullable error) { + if (error) { + XCTAssertNotNil(error); + } else { + XCTAssertNil(error); + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testAssetFetchWithBranch { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Fetch with Branch"]; + + Asset *asset = [self.stack assetWithUID:@"blt240v03mgonxgwvvf"]; + [asset includeBranch]; + + [asset fetch:^(ResponseType type, NSError * _Nullable error) { + if (error) { + XCTAssertNotNil(error); + } else { + XCTAssertNil(error); + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testAssetFetchWithAllOptions { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Fetch with All Options"]; + + Asset *asset = [self.stack assetWithUID:@"blt240v03mgonxgwvvf"]; + [asset includeMetadata]; + [asset includeFallback]; + [asset includeBranch]; + [asset setHeader:@"CustomValue" forKey:@"X-Custom-Header"]; + [asset addParamKey:@"dimension" andValue:@"width=100"]; + + [asset fetch:^(ResponseType type, NSError * _Nullable error) { + // Test completes regardless of result + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +#pragma mark - Asset Properties + +- (void)testAssetProperties { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Properties"]; + + Asset *asset = [self.stack asset]; + + // Configure asset with test data + NSDictionary *testData = @{ + @"uid": @"test123", + @"filename": @"test.jpg", + @"url": @"https://example.com/test.jpg", + @"content_type": @"image/jpeg" + }; + + [asset configureWithDictionary:testData]; + + // Get properties + NSDictionary *properties = [asset properties]; + XCTAssertNotNil(properties, @"Properties should not be nil"); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Asset Edge Cases + +- (void)testAssetWithNilUID { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset with Nil UID"]; + + // Create asset without UID + Asset *asset = [self.stack asset]; + + // Try to fetch (should handle gracefully) + [asset fetch:^(ResponseType type, NSError * _Nullable error) { + // Should get an error + XCTAssertNotNil(error, @"Should have error for asset without UID"); + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testAssetConfigureWithEmptyDictionary { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Configure with Empty Dictionary"]; + + Asset *asset = [self.stack asset]; + [asset configureWithDictionary:@{}]; + + // Should not crash + NSDictionary *properties = [asset properties]; + XCTAssertNotNil(properties, @"Properties should not be nil even with empty config"); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testAssetConfigureWithComplexData { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset Configure with Complex Data"]; + + Asset *asset = [self.stack asset]; + + NSDictionary *complexData = @{ + @"uid": @"asset123", + @"filename": @"test.jpg", + @"url": @"https://example.com/test.jpg", + @"content_type": @"image/jpeg", + @"file_size": @"102400", + @"tags": @[@"tag1", @"tag2"], + @"metadata": @{ + @"width": @800, + @"height": @600 + } + }; + + [asset configureWithDictionary:complexData]; + + // Verify configuration + NSDictionary *properties = [asset properties]; + XCTAssertNotNil(properties); + + [expectation fulfill]; + [self waitForRequest]; +} + +@end + diff --git a/ContentstackTest/AssetLibraryTest.m b/ContentstackTest/AssetLibraryTest.m new file mode 100644 index 0000000..57d16fe --- /dev/null +++ b/ContentstackTest/AssetLibraryTest.m @@ -0,0 +1,564 @@ +// +// AssetLibraryTest.m +// ContentstackTest +// +// Created by Test Suite on 05/11/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import +#import +#import "CSIOInternalHeaders.h" +#import "AssetLibrary.h" +#import "ContentstackDefinitions.h" + +@interface AssetLibraryTest : XCTestCase { + Stack *csStack; + Config *config; +} + +@property (nonatomic, strong) Stack *stack; +@property (nonatomic, strong) AssetLibrary *assetLibrary; + +@end + +@implementation AssetLibraryTest + +- (void)setUp { + [super setUp]; + NSString *path = [[NSBundle bundleForClass:self.class] pathForResource:@"config" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:path]; + NSDictionary *configdict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; + config = [[Config alloc] init]; + config.host = configdict[@"host"]; + self.stack = [Contentstack stackWithAPIKey:configdict[@"api_key"] + accessToken:configdict[@"delivery_token"] + environmentName:configdict[@"environment"] + config:config]; + self.assetLibrary = [self.stack assetLibrary]; +} + +- (void)tearDown { + self.stack = nil; + self.assetLibrary = nil; + [super tearDown]; +} + +#pragma mark - Header Tests + +- (void)testAssetLibrarySetHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"Set Header"]; + + // Set header + [self.assetLibrary setHeader:@"TestValue" forKey:@"Test-Header"]; + + // Verify header is set + NSDictionary *headers = [self.assetLibrary valueForKey:@"localHeaders"]; + XCTAssertNotNil(headers, @"Headers dictionary should not be nil"); + XCTAssertEqualObjects(headers[@"Test-Header"], @"TestValue", @"Header value should match"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testAssetLibraryAddHeadersWithDictionary { + XCTestExpectation *expectation = [self expectationWithDescription:@"Add Headers Dictionary"]; + + // Add multiple headers + NSDictionary *headersToAdd = @{ + @"Header-One": @"Value1", + @"Header-Two": @"Value2", + @"Header-Three": @"Value3" + }; + + [self.assetLibrary addHeadersWithDictionary:headersToAdd]; + + // Verify headers are added + NSDictionary *headers = [self.assetLibrary valueForKey:@"localHeaders"]; + XCTAssertNotNil(headers, @"Headers dictionary should not be nil"); + XCTAssertEqualObjects(headers[@"Header-One"], @"Value1", @"Header-One should match"); + XCTAssertEqualObjects(headers[@"Header-Two"], @"Value2", @"Header-Two should match"); + XCTAssertEqualObjects(headers[@"Header-Three"], @"Value3", @"Header-Three should match"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testAssetLibraryRemoveHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"Remove Header"]; + + // Set header first + [self.assetLibrary setHeader:@"TestValue" forKey:@"Test-Header"]; + + // Verify header is set + NSDictionary *headers = [self.assetLibrary valueForKey:@"localHeaders"]; + XCTAssertEqualObjects(headers[@"Test-Header"], @"TestValue", @"Header should be set"); + + // Remove header + [self.assetLibrary removeHeaderForKey:@"Test-Header"]; + + // Verify header is removed + headers = [self.assetLibrary valueForKey:@"localHeaders"]; + XCTAssertNil(headers[@"Test-Header"], @"Header should be removed"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testAssetLibraryRemoveNonExistentHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"Remove Non-Existent Header"]; + + // Try to remove header that doesn't exist (should not crash) + [self.assetLibrary removeHeaderForKey:@"NonExistent-Header"]; + + // Verify no error + NSDictionary *headers = [self.assetLibrary valueForKey:@"localHeaders"]; + XCTAssertNotNil(headers, @"Headers dictionary should exist"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Sorting Tests + +- (void)testAssetLibrarySortAscending { + XCTestExpectation *expectation = [self expectationWithDescription:@"Sort Ascending"]; + + // Sort by field ascending + [self.assetLibrary sortWithKey:@"created_at" orderBy:OrderByAscending]; + + // Verify sort parameter is set + NSDictionary *params = [self.assetLibrary valueForKey:@"postParamDictionary"]; + XCTAssertNotNil(params[@"asc"], @"Ascending parameter should be set"); + XCTAssertEqualObjects(params[@"asc"], @"created_at", @"Sort field should match"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testAssetLibrarySortDescending { + XCTestExpectation *expectation = [self expectationWithDescription:@"Sort Descending"]; + + // Sort by field descending + [self.assetLibrary sortWithKey:@"updated_at" orderBy:OrderByDescending]; + + // Verify sort parameter is set + NSDictionary *params = [self.assetLibrary valueForKey:@"postParamDictionary"]; + XCTAssertNotNil(params[@"desc"], @"Descending parameter should be set"); + XCTAssertEqualObjects(params[@"desc"], @"updated_at", @"Sort field should match"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Locale Tests + +- (void)testAssetLibrarySetLocale { + XCTestExpectation *expectation = [self expectationWithDescription:@"Set Locale"]; + + // Set locale + [self.assetLibrary locale:@"en-us"]; + + // Verify locale parameter is set + NSDictionary *params = [self.assetLibrary valueForKey:@"postParamDictionary"]; + XCTAssertNotNil(params[@"locale"], @"Locale parameter should be set"); + XCTAssertEqualObjects(params[@"locale"], @"en-us", @"Locale should match"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testAssetLibrarySetDifferentLocales { + XCTestExpectation *expectation = [self expectationWithDescription:@"Set Different Locales"]; + + // Set locale + [self.assetLibrary locale:@"fr-fr"]; + + // Verify first locale + NSDictionary *params = [self.assetLibrary valueForKey:@"postParamDictionary"]; + XCTAssertEqualObjects(params[@"locale"], @"fr-fr", @"Locale should match"); + + // Change locale + [self.assetLibrary locale:@"de-de"]; + + // Verify updated locale + params = [self.assetLibrary valueForKey:@"postParamDictionary"]; + XCTAssertEqualObjects(params[@"locale"], @"de-de", @"Locale should be updated"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Include Methods Tests + +- (void)testAssetLibraryObjectsCount { + XCTestExpectation *expectation = [self expectationWithDescription:@"Objects Count"]; + + // Enable objects count + [self.assetLibrary objectsCount]; + + // Verify parameter is set + NSDictionary *params = [self.assetLibrary valueForKey:@"postParamDictionary"]; + XCTAssertNotNil(params[@"count"], @"Count parameter should be set"); + XCTAssertEqualObjects(params[@"count"], @"true", @"Count should be true"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testAssetLibraryIncludeCount { + XCTestExpectation *expectation = [self expectationWithDescription:@"Include Count"]; + + // Include count + [self.assetLibrary includeCount]; + + // Verify parameter is set + NSDictionary *params = [self.assetLibrary valueForKey:@"postParamDictionary"]; + XCTAssertNotNil(params[@"include_count"], @"Include count parameter should be set"); + XCTAssertEqualObjects(params[@"include_count"], @"true", @"Include count should be true"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testAssetLibraryIncludeRelativeUrls { + XCTestExpectation *expectation = [self expectationWithDescription:@"Include Relative URLs"]; + + // Include relative URLs + [self.assetLibrary includeRelativeUrls]; + + // Verify parameter is set + NSDictionary *params = [self.assetLibrary valueForKey:@"postParamDictionary"]; + XCTAssertNotNil(params[@"relative_urls"], @"Relative URLs parameter should be set"); + XCTAssertEqualObjects(params[@"relative_urls"], @"true", @"Relative URLs should be true"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testAssetLibraryIncludeFallback { + XCTestExpectation *expectation = [self expectationWithDescription:@"Include Fallback"]; + + // Include fallback + [self.assetLibrary includeFallback]; + + // Verify parameter is set + NSDictionary *params = [self.assetLibrary valueForKey:@"postParamDictionary"]; + XCTAssertNotNil(params[@"include_fallback"], @"Include fallback parameter should be set"); + XCTAssertEqualObjects(params[@"include_fallback"], @"true", @"Include fallback should be true"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testAssetLibraryIncludeMetadata { + XCTestExpectation *expectation = [self expectationWithDescription:@"Include Metadata"]; + + // Include metadata + [self.assetLibrary includeMetadata]; + + // Verify parameter is set + NSDictionary *params = [self.assetLibrary valueForKey:@"postParamDictionary"]; + XCTAssertNotNil(params[@"include_metadata"], @"Include metadata parameter should be set"); + XCTAssertEqualObjects(params[@"include_metadata"], @"true", @"Include metadata should be true"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testAssetLibraryIncludeBranch { + XCTestExpectation *expectation = [self expectationWithDescription:@"Include Branch"]; + + // Include branch + [self.assetLibrary includeBranch]; + + // Verify parameter is set + NSDictionary *params = [self.assetLibrary valueForKey:@"postParamDictionary"]; + XCTAssertNotNil(params[@"include_branch"], @"Include branch parameter should be set"); + XCTAssertEqualObjects(params[@"include_branch"], @"true", @"Include branch should be true"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Where Query Tests + +- (void)testAssetLibraryWhereEqualTo { + XCTestExpectation *expectation = [self expectationWithDescription:@"Where Equal To"]; + + // Set where condition + [self.assetLibrary where:@"title" equalTo:@"TestTitle"]; + + // Verify query parameter is set + NSDictionary *params = [self.assetLibrary valueForKey:@"postParamDictionary"]; + NSDictionary *query = params[@"query"]; + XCTAssertNotNil(query, @"Query parameter should be set"); + XCTAssertEqualObjects(query[@"title"], @"TestTitle", @"Query value should match"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testAssetLibraryWhereWithEmptyField { + XCTestExpectation *expectation = [self expectationWithDescription:@"Where with Empty Field"]; + + // Try to set where condition with empty field + [self.assetLibrary where:@"" equalTo:@"TestValue"]; + + // Verify query parameter is not set + NSDictionary *params = [self.assetLibrary valueForKey:@"postParamDictionary"]; + NSDictionary *query = params[@"query"]; + XCTAssertNil(query, @"Query should not be set for empty field"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testAssetLibraryWhereWithNilValue { + XCTestExpectation *expectation = [self expectationWithDescription:@"Where with Nil Value"]; + + // Try to set where condition with nil value + [self.assetLibrary where:@"title" equalTo:nil]; + + // Verify query parameter is not set + NSDictionary *params = [self.assetLibrary valueForKey:@"postParamDictionary"]; + NSDictionary *query = params[@"query"]; + XCTAssertNil(query, @"Query should not be set for nil value"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testAssetLibraryMultipleWhereConditions { + XCTestExpectation *expectation = [self expectationWithDescription:@"Multiple Where Conditions"]; + + // Set multiple where conditions + [self.assetLibrary where:@"title" equalTo:@"TestTitle"]; + [self.assetLibrary where:@"content_type" equalTo:@"image/png"]; + + // Verify both query parameters are set + NSDictionary *params = [self.assetLibrary valueForKey:@"postParamDictionary"]; + NSDictionary *query = params[@"query"]; + XCTAssertNotNil(query, @"Query parameter should be set"); + XCTAssertEqualObjects(query[@"title"], @"TestTitle", @"Title query should match"); + XCTAssertEqualObjects(query[@"content_type"], @"image/png", @"Content type query should match"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testAssetLibraryGetPostParamDictionary { + XCTestExpectation *expectation = [self expectationWithDescription:@"Get Post Param Dictionary"]; + + // Set some parameters + [self.assetLibrary includeCount]; + [self.assetLibrary locale:@"en-us"]; + + // Get post param dictionary + NSDictionary *params = [self.assetLibrary getPostParamDictionary]; + + // Verify parameters + XCTAssertNotNil(params, @"Params should not be nil"); + XCTAssertEqualObjects(params[@"include_count"], @"true", @"Include count should be set"); + XCTAssertEqualObjects(params[@"locale"], @"en-us", @"Locale should be set"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Cache Policy Tests + +- (void)testAssetLibrarySetCachePolicy { + XCTestExpectation *expectation = [self expectationWithDescription:@"Set Cache Policy"]; + + // Set cache policy + self.assetLibrary.cachePolicy = NETWORK_ONLY; + + // Verify cache policy is set + XCTAssertEqual(self.assetLibrary.cachePolicy, NETWORK_ONLY, @"Cache policy should match"); + + // Change cache policy + self.assetLibrary.cachePolicy = CACHE_THEN_NETWORK; + XCTAssertEqual(self.assetLibrary.cachePolicy, CACHE_THEN_NETWORK, @"Cache policy should be updated"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Fetch Tests + +- (void)testAssetLibraryFetchAll { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch All Assets"]; + + // Fetch all assets + [self.assetLibrary fetchAll:^(ResponseType type, NSArray * _Nullable result, NSError * _Nullable error) { + if (error) { + XCTFail(@"Error fetching assets: %@", error); + } else { + XCTAssertNotNil(result, @"Result should not be nil"); + XCTAssertTrue([result isKindOfClass:[NSArray class]], @"Result should be an array"); + } + + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30.0 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +- (void)testAssetLibraryFetchAllWithCount { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch All Assets with Count"]; + + // Include count + [self.assetLibrary includeCount]; + + // Fetch all assets + [self.assetLibrary fetchAll:^(ResponseType type, NSArray * _Nullable result, NSError * _Nullable error) { + if (error) { + XCTFail(@"Error fetching assets: %@", error); + } else { + XCTAssertNotNil(result, @"Result should not be nil"); + } + + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30.0 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +- (void)testAssetLibraryFetchAllWithLocale { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch All Assets with Locale"]; + + // Set locale + [self.assetLibrary locale:@"en-us"]; + + // Fetch all assets + [self.assetLibrary fetchAll:^(ResponseType type, NSArray * _Nullable result, NSError * _Nullable error) { + if (error) { + XCTFail(@"Error fetching assets: %@", error); + } else { + XCTAssertNotNil(result, @"Result should not be nil"); + } + + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30.0 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +- (void)testAssetLibraryFetchAllWithSort { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch All Assets with Sort"]; + + // Sort ascending + [self.assetLibrary sortWithKey:@"created_at" orderBy:OrderByAscending]; + + // Fetch all assets + [self.assetLibrary fetchAll:^(ResponseType type, NSArray * _Nullable result, NSError * _Nullable error) { + if (error) { + XCTFail(@"Error fetching assets: %@", error); + } else { + XCTAssertNotNil(result, @"Result should not be nil"); + } + + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30.0 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +- (void)testAssetLibraryFetchAllWithWhereQuery { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch All Assets with Where Query"]; + + // Set where condition + [self.assetLibrary where:@"content_type" equalTo:@"image/png"]; + + // Fetch all assets + [self.assetLibrary fetchAll:^(ResponseType type, NSArray * _Nullable result, NSError * _Nullable error) { + // Test completes regardless of result (may have no PNG images) + if (!error) { + XCTAssertNotNil(result, @"Result should not be nil on success"); + } + + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30.0 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +- (void)testAssetLibraryFetchAllWithAllOptions { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch All Assets with All Options"]; + + // Set all options + [self.assetLibrary includeCount]; + [self.assetLibrary includeMetadata]; + [self.assetLibrary includeBranch]; + [self.assetLibrary includeFallback]; + [self.assetLibrary includeRelativeUrls]; + [self.assetLibrary locale:@"en-us"]; + [self.assetLibrary sortWithKey:@"updated_at" orderBy:OrderByDescending]; + [self.assetLibrary setHeader:@"CustomValue" forKey:@"X-Custom-Header"]; + + // Fetch all assets + [self.assetLibrary fetchAll:^(ResponseType type, NSArray * _Nullable result, NSError * _Nullable error) { + if (error) { + XCTFail(@"Error fetching assets: %@", error); + } else { + XCTAssertNotNil(result, @"Result should not be nil"); + } + + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30.0 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +#pragma mark - Integration Tests + +- (void)testAssetLibraryMultipleInstances { + XCTestExpectation *expectation = [self expectationWithDescription:@"Multiple AssetLibrary Instances"]; + + // Create multiple asset library instances + AssetLibrary *lib1 = [self.stack assetLibrary]; + AssetLibrary *lib2 = [self.stack assetLibrary]; + + // Set different options on each + [lib1 locale:@"en-us"]; + [lib2 locale:@"fr-fr"]; + + // Verify they're independent + NSDictionary *params1 = [lib1 getPostParamDictionary]; + NSDictionary *params2 = [lib2 getPostParamDictionary]; + + XCTAssertEqualObjects(params1[@"locale"], @"en-us"); + XCTAssertEqualObjects(params2[@"locale"], @"fr-fr"); + XCTAssertNotEqual(lib1, lib2, @"Instances should be different"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +@end + diff --git a/ContentstackTest/ContentTypeEdgeCaseTest.m b/ContentstackTest/ContentTypeEdgeCaseTest.m new file mode 100644 index 0000000..a5f6798 --- /dev/null +++ b/ContentstackTest/ContentTypeEdgeCaseTest.m @@ -0,0 +1,196 @@ +// +// ContentTypeEdgeCaseTest.m +// ContentstackTest +// +// Created by Test Suite on 05/11/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import +#import +#import "CSIOInternalHeaders.h" +#import "ContentstackDefinitions.h" + +static NSInteger kRequestTimeOutInSeconds = 30; + +@interface ContentTypeEdgeCaseTest : XCTestCase { + Stack *csStack; + Config *config; +} + +@property (nonatomic, strong) Stack *stack; + +@end + +@implementation ContentTypeEdgeCaseTest + +- (void)setUp { + [super setUp]; + NSString *path = [[NSBundle bundleForClass:self.class] pathForResource:@"config" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:path]; + NSDictionary *configdict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; + config = [[Config alloc] init]; + config.host = configdict[@"host"]; + self.stack = [Contentstack stackWithAPIKey:configdict[@"api_key"] + accessToken:configdict[@"delivery_token"] + environmentName:configdict[@"environment"] + config:config]; +} + +- (void)waitForRequest { + [self waitForExpectationsWithTimeout:kRequestTimeOutInSeconds handler:^(NSError *error) { + if (error) { + XCTFail(@"Could not perform operation (Timed out) ~ ERR: %@", error.userInfo); + } + }]; +} + +- (void)tearDown { + self.stack = nil; + [super tearDown]; +} + +#pragma mark - ContentType Header Tests + +- (void)testContentTypeSetHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"ContentType Set Header"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + [contentType setHeader:@"TestValue" forKey:@"X-Test-Header"]; + XCTAssertNotNil(contentType); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testContentTypeAddHeadersWithDictionary { + XCTestExpectation *expectation = [self expectationWithDescription:@"ContentType Add Headers"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + + NSDictionary *headers = @{ + @"Header-1": @"Value1", + @"Header-2": @"Value2" + }; + + [contentType addHeadersWithDictionary:headers]; + XCTAssertNotNil(contentType); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testContentTypeRemoveHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"ContentType Remove Header"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + + [contentType setHeader:@"TestValue" forKey:@"X-Test-Header"]; + [contentType removeHeaderForKey:@"X-Test-Header"]; + XCTAssertNotNil(contentType); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - ContentType Fetch Tests + +- (void)testContentTypeFetch { + XCTestExpectation *expectation = [self expectationWithDescription:@"ContentType Fetch"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + + [contentType fetch:nil completion:^(NSDictionary * _Nullable contentTypeDict, NSError * _Nullable error) { + if (error) { + // May fail if content type doesn't exist + } else { + XCTAssertNotNil(contentTypeDict); + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testContentTypeFetchWithHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"ContentType Fetch with Header"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + [contentType setHeader:@"CustomValue" forKey:@"X-Custom-Header"]; + + [contentType fetch:nil completion:^(NSDictionary * _Nullable contentTypeDict, NSError * _Nullable error) { + if (error) { + // May fail + } else { + XCTAssertNotNil(contentTypeDict); + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +#pragma mark - ContentType Entry Creation + +- (void)testContentTypeCreateEntry { + XCTestExpectation *expectation = [self expectationWithDescription:@"ContentType Create Entry"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + XCTAssertNotNil(entry, @"Entry should not be nil"); + XCTAssertTrue([entry isKindOfClass:[Entry class]]); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testContentTypeCreateEntryWithUID { + XCTestExpectation *expectation = [self expectationWithDescription:@"ContentType Create Entry with UID"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entryWithUID:@"test_uid"]; + + XCTAssertNotNil(entry); + XCTAssertTrue([entry isKindOfClass:[Entry class]]); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - ContentType Query Creation + +- (void)testContentTypeCreateQuery { + XCTestExpectation *expectation = [self expectationWithDescription:@"ContentType Create Query"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + XCTAssertNotNil(query, @"Query should not be nil"); + XCTAssertTrue([query isKindOfClass:[Query class]]); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - ContentType Multiple Instances + +- (void)testMultipleContentTypeInstances { + XCTestExpectation *expectation = [self expectationWithDescription:@"Multiple ContentType Instances"]; + + ContentType *ct1 = [self.stack contentTypeWithName:@"source"]; + ContentType *ct2 = [self.stack contentTypeWithName:@"source"]; + + // Set different headers + [ct1 setHeader:@"Value1" forKey:@"Test"]; + [ct2 setHeader:@"Value2" forKey:@"Test"]; + + XCTAssertNotNil(ct1); + XCTAssertNotNil(ct2); + + [expectation fulfill]; + [self waitForRequest]; +} + +@end + diff --git a/ContentstackTest/ContentstackMainTest.m b/ContentstackTest/ContentstackMainTest.m new file mode 100644 index 0000000..a53cfe8 --- /dev/null +++ b/ContentstackTest/ContentstackMainTest.m @@ -0,0 +1,164 @@ +// +// ContentstackMainTest.m +// ContentstackTest +// +// Created by Contentstack on 06/11/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import +#import "Contentstack.h" +#import "Stack.h" +#import "Config.h" + +@interface ContentstackMainTest : XCTestCase +@property (nonatomic, strong) NSDictionary *config; +@end + +@implementation ContentstackMainTest + +- (void)setUp { + [super setUp]; + + NSString *filePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"config" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:filePath]; + if (data) { + self.config = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + } +} + +- (void)tearDown { + self.config = nil; + [super tearDown]; +} + +#pragma mark - Stack Creation Tests + +- (void)testStackWithAPIKeyAccessToken { + Stack *stack = [Contentstack stackWithAPIKey:self.config[@"api_key"] + accessToken:self.config[@"delivery_token"] + environmentName:self.config[@"environment"]]; + + XCTAssertNotNil(stack); + XCTAssertTrue([stack isKindOfClass:[Stack class]]); +} + +- (void)testStackWithAPIKeyAccessTokenConfig { + Config *config = [[Config alloc] init]; + config.host = self.config[@"host"]; + + Stack *stack = [Contentstack stackWithAPIKey:self.config[@"api_key"] + accessToken:self.config[@"delivery_token"] + environmentName:self.config[@"environment"] + config:config]; + + XCTAssertNotNil(stack); + XCTAssertTrue([stack isKindOfClass:[Stack class]]); +} + +- (void)testStackCreationWithDifferentRegions { + Config *configUS = [[Config alloc] init]; + configUS.region = US; + + Stack *stackUS = [Contentstack stackWithAPIKey:self.config[@"api_key"] + accessToken:self.config[@"delivery_token"] + environmentName:self.config[@"environment"] + config:configUS]; + + XCTAssertNotNil(stackUS); + + Config *configEU = [[Config alloc] init]; + configEU.region = EU; + + Stack *stackEU = [Contentstack stackWithAPIKey:self.config[@"api_key"] + accessToken:self.config[@"delivery_token"] + environmentName:self.config[@"environment"] + config:configEU]; + + XCTAssertNotNil(stackEU); +} + +- (void)testMultipleStackInstances { + Stack *stack1 = [Contentstack stackWithAPIKey:self.config[@"api_key"] + accessToken:self.config[@"delivery_token"] + environmentName:self.config[@"environment"]]; + + Stack *stack2 = [Contentstack stackWithAPIKey:self.config[@"api_key"] + accessToken:self.config[@"delivery_token"] + environmentName:self.config[@"environment"]]; + + XCTAssertNotNil(stack1); + XCTAssertNotNil(stack2); + // Each call creates a new instance + XCTAssertNotEqual(stack1, stack2); +} + +- (void)testStackWithCustomHost { + Config *config = [[Config alloc] init]; + config.host = @"custom-cdn.contentstack.io"; + + Stack *stack = [Contentstack stackWithAPIKey:self.config[@"api_key"] + accessToken:self.config[@"delivery_token"] + environmentName:self.config[@"environment"] + config:config]; + + XCTAssertNotNil(stack); +} + +- (void)testStackWithEmptyEnvironment { + Stack *stack = [Contentstack stackWithAPIKey:self.config[@"api_key"] + accessToken:self.config[@"delivery_token"] + environmentName:@""]; + + XCTAssertNotNil(stack); +} + +#pragma mark - Config Tests + +- (void)testConfigRegions { + Config *config = [[Config alloc] init]; + + config.region = US; + XCTAssertEqual(config.region, US); + + config.region = EU; + XCTAssertEqual(config.region, EU); + + config.region = AZURE_NA; + XCTAssertEqual(config.region, AZURE_NA); +} + +- (void)testConfigHost { + Config *config = [[Config alloc] init]; + + config.host = @"test-host.contentstack.io"; + XCTAssertEqualObjects(config.host, @"test-host.contentstack.io"); +} + +- (void)testConfigVersionReadonly { + Config *config = [[Config alloc] init]; + + // Version is readonly, just verify it exists + XCTAssertNotNil(config.version); +} + +- (void)testConfigBranchWritable { + Config *config = [[Config alloc] init]; + + config.branch = @"development"; + XCTAssertEqualObjects(config.branch, @"development"); +} + +#pragma mark - Request Cancellation Tests + +- (void)testCancelAllRequestsOfStack { + Stack *stack = [Contentstack stackWithAPIKey:self.config[@"api_key"] + accessToken:self.config[@"delivery_token"] + environmentName:self.config[@"environment"]]; + + // Should not crash when cancelling requests + XCTAssertNoThrow([Contentstack cancelAllRequestsOfStack:stack]); +} + +@end + diff --git a/ContentstackTest/EntryAdvancedTest.m b/ContentstackTest/EntryAdvancedTest.m new file mode 100644 index 0000000..a219757 --- /dev/null +++ b/ContentstackTest/EntryAdvancedTest.m @@ -0,0 +1,264 @@ +// +// EntryAdvancedTest.m +// ContentstackTest +// +// Created by Contentstack on 06/11/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import +#import "Contentstack.h" +#import "Entry.h" +#import "ContentType.h" +#import "Config.h" +#import "CSIOInternalHeaders.h" + +@interface EntryAdvancedTest : XCTestCase +@property (nonatomic, strong) Stack *stack; +@property (nonatomic, strong) Entry *entry; +@end + +@implementation EntryAdvancedTest + +- (void)setUp { + [super setUp]; + + NSString *filePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"config" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:filePath]; + if (data) { + NSDictionary *config = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + + Config *conf = [[Config alloc] init]; + conf.host = config[@"host"]; + + self.stack = [Contentstack stackWithAPIKey:config[@"api_key"] + accessToken:config[@"delivery_token"] + environmentName:config[@"environment"] + config:conf]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + self.entry = [contentType entryWithUID:@"test_uid"]; + } +} + +- (void)tearDown { + self.entry = nil; + self.stack = nil; + [super tearDown]; +} + +- (NSTimeInterval)requestTimeout { + return 60.0; +} + +- (void)waitForRequest { + NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:[self requestTimeout]]; + while ([self.entry valueForKey:@"requestOperation"] != nil && [timeoutDate timeIntervalSinceNow] > 0) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; + } +} + +#pragma mark - Dictionary Access Tests + +- (void)testObjectForKeyNonExistent { + id value = [self.entry objectForKey:@"nonexistent_key"]; + // Entry without fetch will return nil + XCTAssertTrue(value == nil || [value isKindOfClass:[NSObject class]]); +} + +- (void)testObjectForKeyedSubscriptNil { + id value = self.entry[@"nonexistent"]; + // Entry without fetch will return nil + XCTAssertTrue(value == nil || [value isKindOfClass:[NSObject class]]); +} + +- (void)testObjectForKeyMethod { + // Test that objectForKey: method exists and doesn't crash + XCTAssertNoThrow([self.entry objectForKey:@"any_key"]); +} + +- (void)testObjectForKeyedSubscriptMethod { + // Test that subscript access works + XCTAssertNoThrow(self.entry[@"any_key"]); +} + +#pragma mark - Has Key Tests + +- (void)testHasKeyDoesNotExist { + // Without fetch, entry has no data + BOOL hasKey = [self.entry hasKey:@"nonexistent_key"]; + XCTAssertFalse(hasKey); +} + +- (void)testHasKeyWithNil { + BOOL hasKey = [self.entry hasKey:nil]; + XCTAssertFalse(hasKey); +} + +- (void)testHasKeyMethod { + // Test that hasKey: method exists and doesn't crash + XCTAssertNoThrow([self.entry hasKey:@"any_key"]); +} + +#pragma mark - Markdown Conversion Tests + +- (void)testHTMLStringForMarkdownKeyNonExistent { + // Without data, should return nil + NSString *html = [self.entry HTMLStringForMarkdownKey:@"nonexistent"]; + XCTAssertNil(html); +} + +- (void)testHTMLStringForMarkdownKeyMethod { + // Test that the method exists and doesn't crash + XCTAssertNoThrow([self.entry HTMLStringForMarkdownKey:@"any_key"]); +} + +- (void)testHTMLArrayForMarkdownKeyNonExistent { + // Without data, should return nil + NSArray *htmlArray = [self.entry HTMLArrayForMarkdownKey:@"nonexistent"]; + XCTAssertNil(htmlArray); +} + +- (void)testHTMLArrayForMarkdownKeyMethod { + // Test that the method exists and doesn't crash + XCTAssertNoThrow([self.entry HTMLArrayForMarkdownKey:@"any_key"]); +} + +#pragma mark - Asset Retrieval Tests + +- (void)testAssetForKeyNonExistent { + // Without data, should return nil + Asset *asset = [self.entry assetForKey:@"nonexistent_asset"]; + XCTAssertNil(asset); +} + +- (void)testAssetForKeyMethod { + // Test that the method exists and doesn't crash + XCTAssertNoThrow([self.entry assetForKey:@"any_key"]); +} + +- (void)testAssetsForKeyNonExistent { + // Without data, should return empty array (not nil) for backward compatibility + NSArray *assets = [self.entry assetsForKey:@"nonexistent_assets"]; + XCTAssertNotNil(assets); + XCTAssertEqual(assets.count, 0); +} + +- (void)testAssetsForKeyMethod { + // Test that the method exists and doesn't crash + XCTAssertNoThrow([self.entry assetsForKey:@"any_key"]); +} + +#pragma mark - Group Retrieval Tests + +- (void)testGroupsForKeyNonExistent { + // Without data, should return nil + NSArray *groups = [self.entry groupsForKey:@"nonexistent_groups"]; + XCTAssertNil(groups); +} + +- (void)testGroupsForKeyMethod { + // Test that the method exists and doesn't crash + XCTAssertNoThrow([self.entry groupsForKey:@"any_key"]); +} + +#pragma mark - Reference Entry Tests + +- (void)testEntriesForKeyWithContentTypeNonExistent { + // Without data, should return nil + NSArray *entries = [self.entry entriesForKey:@"nonexistent_ref" withContentType:@"ref_ct"]; + XCTAssertNil(entries); +} + +- (void)testEntriesForKeyMethod { + // Test that the method exists and doesn't crash + XCTAssertNoThrow([self.entry entriesForKey:@"any_key" withContentType:@"any_ct"]); +} + +#pragma mark - Description Test + +- (void)testEntryDescription { + NSString *description = [self.entry description]; + + XCTAssertNotNil(description); + XCTAssertTrue([description containsString:@"Entry"]); +} + +#pragma mark - Initialization Tests + +- (void)testInitWithContentTypeOnly { + ContentType *contentType = [self.stack contentTypeWithName:@"test"]; + Entry *entry = [contentType entryWithUID: @"test"]; + + XCTAssertNotNil(entry); + XCTAssertEqualObjects(entry.contentTypeName,@"test"); +} + +- (void)testInitWithTaxonomy { + Taxonomy *taxonomy = [[Taxonomy alloc] initWithStack:self.stack]; + Entry *entry = [[Entry alloc] initWithTaxonomy:taxonomy]; + + XCTAssertNotNil(entry); +} + +#pragma mark - Include Methods Tests + +- (void)testIncludeFallback { + [self.entry includeFallback]; + XCTAssertNotNil(self.entry); +} + +- (void)testIncludeMetadata { + [self.entry includeMetadata]; + XCTAssertNotNil(self.entry); +} + +- (void)testIncludeBranch { + [self.entry includeBranch]; + XCTAssertNotNil(self.entry); +} + +- (void)testGroupForKey { + Group *group = [self.entry groupForKey:@"any_group_key"]; + // Without data, should return nil + XCTAssertTrue(group == nil || [group isKindOfClass:[Group class]]); +} + +- (void)testConfigureWithDictionary { + NSDictionary *testDict = @{ + @"title": @"Test Title", + @"description": @"Test Description" + }; + + [self.entry configureWithDictionary:testDict]; + XCTAssertNotNil(self.entry); +} + +- (void)testConfigureWithEmptyDictionary { + [self.entry configureWithDictionary:@{}]; + XCTAssertNotNil(self.entry); +} + +- (void)testEntryProperties { + // Test that readonly properties exist and can be accessed + XCTAssertNotNil(self.entry.uid); + XCTAssertNotNil(self.entry.contentTypeName); + // Other properties may be nil until fetch + XCTAssertTrue(YES); +} + +- (void)testEntryCachePolicy { + self.entry.cachePolicy = CACHE_THEN_NETWORK; + XCTAssertEqual(self.entry.cachePolicy, CACHE_THEN_NETWORK); + + self.entry.cachePolicy = NETWORK_ONLY; + XCTAssertEqual(self.entry.cachePolicy, NETWORK_ONLY); +} + +- (void)testEntryLocale { + self.entry.locale = @"en-us"; + XCTAssertEqualObjects(self.entry.locale, @"en-us"); +} + +@end + diff --git a/ContentstackTest/EntryEdgeCaseTest.m b/ContentstackTest/EntryEdgeCaseTest.m new file mode 100644 index 0000000..69ab029 --- /dev/null +++ b/ContentstackTest/EntryEdgeCaseTest.m @@ -0,0 +1,481 @@ +// +// EntryEdgeCaseTest.m +// ContentstackTest +// +// Created by Test Suite on 05/11/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import +#import +#import "CSIOInternalHeaders.h" +#import "ContentstackDefinitions.h" + +static NSInteger kRequestTimeOutInSeconds = 30; + +@interface EntryEdgeCaseTest : XCTestCase { + Stack *csStack; + Config *config; +} + +@property (nonatomic, strong) Stack *stack; + +@end + +@implementation EntryEdgeCaseTest + +- (void)setUp { + [super setUp]; + NSString *path = [[NSBundle bundleForClass:self.class] pathForResource:@"config" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:path]; + NSDictionary *configdict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; + config = [[Config alloc] init]; + config.host = configdict[@"host"]; + self.stack = [Contentstack stackWithAPIKey:configdict[@"api_key"] + accessToken:configdict[@"delivery_token"] + environmentName:configdict[@"environment"] + config:config]; +} + +- (void)waitForRequest { + [self waitForExpectationsWithTimeout:kRequestTimeOutInSeconds handler:^(NSError *error) { + if (error) { + XCTFail(@"Could not perform operation (Timed out) ~ ERR: %@", error.userInfo); + } + }]; +} + +- (void)tearDown { + self.stack = nil; + [super tearDown]; +} + +#pragma mark - Entry Header Tests + +- (void)testEntrySetHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Set Header"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + // Test that method exists and can be called + [entry setHeader:@"TestValue" forKey:@"X-Test-Header"]; + + // Method should not crash - header will be sent with fetch + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testEntryAddHeadersWithDictionary { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Add Headers Dictionary"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + NSDictionary *headersToAdd = @{ + @"Header-1": @"Value1", + @"Header-2": @"Value2" + }; + + // Test that method exists and can be called + [entry addHeadersWithDictionary:headersToAdd]; + + // Method should not crash - headers will be sent with fetch + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testEntryRemoveHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Remove Header"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + // Add header first + [entry setHeader:@"TestValue" forKey:@"X-Test-Header"]; + + // Remove header - test that method exists and can be called + [entry removeHeaderForKey:@"X-Test-Header"]; + + // Method should not crash + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Entry Method Existence Tests + +- (void)testEntryVariantUid { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Variant UID"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + // Test that method exists and can be called + [entry variantUid:@"variant123"]; + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testEntryVariantUids { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Variant UIDs"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + NSArray *variantUids = @[@"variant1", @"variant2", @"variant3"]; + + // Test that method exists and can be called + [entry variantUids:variantUids]; + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testEntryAddParamKeyValue { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Add Param"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + // Test that method exists and can be called + [entry addParamKey:@"custom_param" andValue:@"custom_value"]; + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testEntryIncludeSchema { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Include Schema"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + // Test that method exists and can be called + [entry includeSchema]; + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testEntryIncludeContentType { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Include Content Type"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + // Test that method exists and can be called + [entry includeContentType]; + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testEntryIncludeReferenceContentTypeUid { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Include Reference Content Type UID"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + // Test that method exists and can be called + [entry includeReferenceContentTypeUid]; + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testEntryIncludeEmbeddedItems { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Include Embedded Items"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + // Test that method exists and can be called + [entry includeEmbeddedItems]; + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testEntryIncludeOnlyFields { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Include Only Fields"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + NSArray *fields = @[@"title", @"description"]; + + // Test that method exists and can be called + [entry includeOnlyFields:fields]; + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testEntryIncludeAllFieldsExcept { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Include All Fields Except"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + NSArray *fields = @[@"sensitive_field", @"internal_field"]; + + // Test that method exists and can be called + [entry includeAllFieldsExcept:fields]; + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testEntryIncludeRefFieldWithKey { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Include Ref Field"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + NSArray *keys = @[@"reference_field"]; + + // Test that method exists and can be called + [entry includeRefFieldWithKey:keys]; + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testEntryIncludeRefFieldWithKeyAndOnlyRefValues { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Include Ref Field with Only Values"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + NSArray *values = @[@"title", @"uid"]; + + // Test that method exists and can be called + [entry includeRefFieldWithKey:@"reference_field" andOnlyRefValuesWithKeys:values]; + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testEntryIncludeRefFieldWithKeyExcludingRefValues { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Include Ref Field Excluding Values"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + NSArray *values = @[@"internal_field"]; + + // Test that method exists and can be called + [entry includeRefFieldWithKey:@"reference_field" excludingRefValuesWithKeys:values]; + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Entry Fetch with Options + +- (void)testEntryFetchWithSchema { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Fetch with Schema"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entryWithUID:@"blt8e2851fc0785e7c4"]; + + [entry includeSchema]; + + [entry fetch:^(ResponseType type, NSError * _Nullable error) { + if (error) { + // Entry UID may not exist + XCTAssertNotNil(error); + } else { + XCTAssertNil(error); + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testEntryFetchWithContentType { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Fetch with Content Type"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entryWithUID:@"blt8e2851fc0785e7c4"]; + + [entry includeContentType]; + + [entry fetch:^(ResponseType type, NSError * _Nullable error) { + if (error) { + XCTAssertNotNil(error); + } else { + XCTAssertNil(error); + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testEntryFetchWithReferenceContentTypeUid { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Fetch with Reference Content Type UID"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entryWithUID:@"blt8e2851fc0785e7c4"]; + + [entry includeReferenceContentTypeUid]; + + [entry fetch:^(ResponseType type, NSError * _Nullable error) { + if (error) { + XCTAssertNotNil(error); + } else { + XCTAssertNil(error); + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testEntryFetchWithEmbeddedItems { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Fetch with Embedded Items"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entryWithUID:@"blt8e2851fc0785e7c4"]; + + [entry includeEmbeddedItems]; + + [entry fetch:^(ResponseType type, NSError * _Nullable error) { + if (error) { + XCTAssertNotNil(error); + } else { + XCTAssertNil(error); + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testEntryFetchWithVariant { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Fetch with Variant"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entryWithUID:@"blt8e2851fc0785e7c4"]; + + [entry variantUid:@"test_variant"]; + + [entry fetch:^(ResponseType type, NSError * _Nullable error) { + // May fail if variant doesn't exist + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testEntryFetchWithAllOptions { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Fetch with All Options"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entryWithUID:@"blt8e2851fc0785e7c4"]; + + [entry includeSchema]; + [entry includeContentType]; + [entry includeReferenceContentTypeUid]; + [entry includeEmbeddedItems]; + [entry setHeader:@"CustomValue" forKey:@"X-Custom-Header"]; + [entry addParamKey:@"custom_param" andValue:@"custom_value"]; + + [entry fetch:^(ResponseType type, NSError * _Nullable error) { + // Test completes regardless of result + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +#pragma mark - Entry Cancel Request + +- (void)testEntryCancelRequest { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Cancel Request"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entryWithUID:@"blt8e2851fc0785e7c4"]; + + // Start fetch + [entry fetch:^(ResponseType type, NSError * _Nullable error) { + // May or may not be called + }]; + + // Cancel immediately + [entry cancelRequest]; + + // Wait a bit to ensure no crash + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [expectation fulfill]; + }); + + [self waitForRequest]; +} + +#pragma mark - Entry Edge Cases + +- (void)testEntryConfigureWithEmptyDictionary { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Configure with Empty Dictionary"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + [entry configureWithDictionary:@{}]; + + // Should not crash + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testEntryConfigureWithComplexData { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entry Configure with Complex Data"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Entry *entry = [contentType entry]; + + NSDictionary *complexData = @{ + @"uid": @"entry123", + @"title": @"Test Entry", + @"description": @"Test Description", + @"tags": @[@"tag1", @"tag2"], + @"nested": @{ + @"field1": @"value1", + @"field2": @"value2" + } + }; + + [entry configureWithDictionary:complexData]; + + // Verify configuration + XCTAssertNotNil(entry); + + [expectation fulfill]; + [self waitForRequest]; +} + +@end + diff --git a/ContentstackTest/GroupTest.m b/ContentstackTest/GroupTest.m new file mode 100644 index 0000000..ca8a076 --- /dev/null +++ b/ContentstackTest/GroupTest.m @@ -0,0 +1,448 @@ +// +// GroupTest.m +// ContentstackTest +// +// Created by Test Suite on 05/11/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import +#import +#import "CSIOInternalHeaders.h" +#import "Group.h" +#import "ContentstackDefinitions.h" + +@interface GroupTest : XCTestCase { + Stack *csStack; + Config *config; +} + +@property (nonatomic, strong) Stack *stack; +@property (nonatomic, strong) Group *group; + +@end + +@implementation GroupTest + +- (void)setUp { + [super setUp]; + NSString *path = [[NSBundle bundleForClass:self.class] pathForResource:@"config" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:path]; + NSDictionary *configdict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; + config = [[Config alloc] init]; + config.host = configdict[@"host"]; + self.stack = [Contentstack stackWithAPIKey:configdict[@"api_key"] + accessToken:configdict[@"delivery_token"] + environmentName:configdict[@"environment"] + config:config]; +} + +- (void)tearDown { + self.stack = nil; + self.group = nil; + [super tearDown]; +} + +#pragma mark - Initialization Tests + +- (void)testGroupInitWithStack { + XCTestExpectation *expectation = [self expectationWithDescription:@"Init with Stack"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + + XCTAssertNotNil(self.group, @"Group should not be nil"); + XCTAssertEqual([self.group valueForKey:@"stack"], self.stack, @"Stack should be set"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGroupInitWithStackAndField { + XCTestExpectation *expectation = [self expectationWithDescription:@"Init with Stack and Field"]; + + self.group = [[Group alloc] initWithStack:self.stack andField:@"testField"]; + + XCTAssertNotNil(self.group, @"Group should not be nil"); + XCTAssertEqualObjects([self.group valueForKey:@"fieldName"], @"testField", @"Field name should be set"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Configure Tests + +- (void)testGroupConfigureWithDictionary { + XCTestExpectation *expectation = [self expectationWithDescription:@"Configure with Dictionary"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + + NSDictionary *testData = @{ + @"title": @"Test Title", + @"description": @"Test Description", + @"count": @42 + }; + + [self.group configureWithDictionary:testData]; + + // Verify data is configured + XCTAssertEqualObjects([self.group objectForKey:@"title"], @"Test Title", @"Title should match"); + XCTAssertEqualObjects([self.group objectForKey:@"description"], @"Test Description", @"Description should match"); + XCTAssertEqualObjects([self.group objectForKey:@"count"], @42, @"Count should match"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGroupReconfigureWithDictionary { + XCTestExpectation *expectation = [self expectationWithDescription:@"Reconfigure with Dictionary"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + + // Configure first time + NSDictionary *firstData = @{@"key1": @"value1"}; + [self.group configureWithDictionary:firstData]; + XCTAssertEqualObjects([self.group objectForKey:@"key1"], @"value1"); + + // Configure second time (should replace) + NSDictionary *secondData = @{@"key2": @"value2"}; + [self.group configureWithDictionary:secondData]; + + // Old key should be gone + XCTAssertNil([self.group objectForKey:@"key1"], @"Old key should be removed"); + XCTAssertEqualObjects([self.group objectForKey:@"key2"], @"value2", @"New key should be set"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Object Access Tests + +- (void)testGroupObjectForKey { + XCTestExpectation *expectation = [self expectationWithDescription:@"Object for Key"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + + NSDictionary *testData = @{ + @"stringValue": @"Test", + @"numberValue": @123, + @"arrayValue": @[@"one", @"two", @"three"] + }; + + [self.group configureWithDictionary:testData]; + + // Test different types + XCTAssertEqualObjects([self.group objectForKey:@"stringValue"], @"Test"); + XCTAssertEqualObjects([self.group objectForKey:@"numberValue"], @123); + XCTAssertTrue([[self.group objectForKey:@"arrayValue"] isKindOfClass:[NSArray class]]); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGroupObjectForNonExistentKey { + XCTestExpectation *expectation = [self expectationWithDescription:@"Object for Non-Existent Key"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + [self.group configureWithDictionary:@{@"key": @"value"}]; + + // Test non-existent key + id result = [self.group objectForKey:@"nonExistentKey"]; + XCTAssertNil(result, @"Should return nil for non-existent key"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Sub-Group Tests + +- (void)testGroupForKey { + XCTestExpectation *expectation = [self expectationWithDescription:@"Group for Key"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + + NSDictionary *subGroupData = @{ + @"subTitle": @"Sub Title", + @"subValue": @100 + }; + + [self.group configureWithDictionary:@{@"subGroup": subGroupData}]; + + // Get sub-group + Group *subGroup = [self.group groupForKey:@"subGroup"]; + XCTAssertNotNil(subGroup, @"Sub-group should not be nil"); + XCTAssertTrue([subGroup isKindOfClass:[Group class]], @"Should be Group instance"); + XCTAssertEqualObjects([subGroup objectForKey:@"subTitle"], @"Sub Title"); + XCTAssertEqualObjects([subGroup objectForKey:@"subValue"], @100); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGroupForKeyWithNonDictionary { + XCTestExpectation *expectation = [self expectationWithDescription:@"Group for Key with Non-Dictionary"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + [self.group configureWithDictionary:@{@"notAGroup": @"stringValue"}]; + + // Try to get group for non-dictionary value + Group *result = [self.group groupForKey:@"notAGroup"]; + XCTAssertNil(result, @"Should return nil for non-dictionary value"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGroupsForKey { + XCTestExpectation *expectation = [self expectationWithDescription:@"Groups for Key"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + + NSArray *subGroupsData = @[ + @{@"name": @"Group 1", @"value": @10}, + @{@"name": @"Group 2", @"value": @20}, + @{@"name": @"Group 3", @"value": @30} + ]; + + [self.group configureWithDictionary:@{@"subGroups": subGroupsData}]; + + // Get multiple sub-groups + NSArray *subGroups = [self.group groupsForKey:@"subGroups"]; + XCTAssertNotNil(subGroups, @"Sub-groups array should not be nil"); + XCTAssertEqual(subGroups.count, 3, @"Should have 3 sub-groups"); + + Group *firstGroup = subGroups[0]; + XCTAssertTrue([firstGroup isKindOfClass:[Group class]]); + XCTAssertEqualObjects([firstGroup objectForKey:@"name"], @"Group 1"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGroupsForKeyWithNonArray { + XCTestExpectation *expectation = [self expectationWithDescription:@"Groups for Key with Non-Array"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + [self.group configureWithDictionary:@{@"notAnArray": @"stringValue"}]; + + // Try to get groups for non-array value + NSArray *result = [self.group groupsForKey:@"notAnArray"]; + XCTAssertNil(result, @"Should return nil for non-array value"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Asset Tests + +- (void)testGroupAssetForKey { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset for Key"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + + NSDictionary *assetData = @{ + @"uid": @"asset123", + @"filename": @"test.jpg", + @"url": @"https://example.com/test.jpg" + }; + + [self.group configureWithDictionary:@{@"image": assetData}]; + + // Get asset + Asset *asset = [self.group assetForKey:@"image"]; + XCTAssertNotNil(asset, @"Asset should not be nil"); + XCTAssertTrue([asset isKindOfClass:[Asset class]], @"Should be Asset instance"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGroupAssetForKeyWithNonDictionary { + XCTestExpectation *expectation = [self expectationWithDescription:@"Asset for Key with Non-Dictionary"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + [self.group configureWithDictionary:@{@"notAnAsset": @"stringValue"}]; + + // Try to get asset for non-dictionary value + Asset *result = [self.group assetForKey:@"notAnAsset"]; + XCTAssertNil(result, @"Should return nil for non-dictionary value"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGroupAssetsForKey { + XCTestExpectation *expectation = [self expectationWithDescription:@"Assets for Key"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + + NSArray *assetsData = @[ + @{@"uid": @"asset1", @"filename": @"img1.jpg"}, + @{@"uid": @"asset2", @"filename": @"img2.jpg"}, + @{@"uid": @"asset3", @"filename": @"img3.jpg"} + ]; + + [self.group configureWithDictionary:@{@"images": assetsData}]; + + // Get multiple assets + NSArray *assets = [self.group assetsForKey:@"images"]; + XCTAssertNotNil(assets, @"Assets array should not be nil"); + XCTAssertEqual(assets.count, 3, @"Should have 3 assets"); + XCTAssertTrue([assets[0] isKindOfClass:[Asset class]]); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGroupAssetsForKeyWithNonArray { + XCTestExpectation *expectation = [self expectationWithDescription:@"Assets for Key with Non-Array"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + [self.group configureWithDictionary:@{@"notAnArray": @"stringValue"}]; + + // Try to get assets for non-array value + NSArray *result = [self.group assetsForKey:@"notAnArray"]; + XCTAssertTrue(result.count == 0, @"Should return empty array for non-array value"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Markdown Tests + +- (void)testGroupHTMLStringForMarkdownKey { + XCTestExpectation *expectation = [self expectationWithDescription:@"HTML String for Markdown"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + + NSString *markdown = @"# Test Header\n\nThis is **bold** text."; + [self.group configureWithDictionary:@{@"markdown": markdown}]; + + // Convert markdown to HTML + NSString *html = [self.group HTMLStringForMarkdownKey:@"markdown"]; + XCTAssertNotNil(html, @"HTML should not be nil"); + XCTAssertTrue([html containsString:@"

"], @"Should contain HTML header tag"); + XCTAssertTrue([html containsString:@""], @"Should contain bold tag"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGroupHTMLStringForMarkdownKeyWithEmptyString { + XCTestExpectation *expectation = [self expectationWithDescription:@"HTML String for Empty Markdown"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + [self.group configureWithDictionary:@{@"markdown": @""}]; + + // Try to convert empty markdown + NSString *html = [self.group HTMLStringForMarkdownKey:@"markdown"]; + XCTAssertNil(html, @"HTML should be nil for empty markdown"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGroupHTMLStringForNonExistentKey { + XCTestExpectation *expectation = [self expectationWithDescription:@"HTML String for Non-Existent Key"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + [self.group configureWithDictionary:@{@"key": @"value"}]; + + // Try to convert non-existent key + NSString *html = [self.group HTMLStringForMarkdownKey:@"nonExistent"]; + XCTAssertNil(html, @"HTML should be nil for non-existent key"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGroupHTMLArrayForMarkdownKey { + XCTestExpectation *expectation = [self expectationWithDescription:@"HTML Array for Markdown"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + + NSArray *markdownArray = @[ + @"# Header 1", + @"# Header 2", + @"# Header 3" + ]; + + [self.group configureWithDictionary:@{@"markdowns": markdownArray}]; + + // Convert markdown array to HTML array + NSArray *htmlArray = [self.group HTMLArrayForMarkdownKey:@"markdowns"]; + XCTAssertNotNil(htmlArray, @"HTML array should not be nil"); + XCTAssertEqual(htmlArray.count, 3, @"Should have 3 HTML strings"); + XCTAssertTrue([htmlArray[0] containsString:@"

"], @"First item should contain HTML header"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGroupHTMLArrayForMarkdownKeyWithNonArray { + XCTestExpectation *expectation = [self expectationWithDescription:@"HTML Array for Non-Array"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + [self.group configureWithDictionary:@{@"notAnArray": @"string"}]; + + // Try to convert non-array + NSArray *htmlArray = [self.group HTMLArrayForMarkdownKey:@"notAnArray"]; + XCTAssertNil(htmlArray, @"HTML array should be nil for non-array value"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Entry Reference Tests + +- (void)testGroupEntriesForKeyWithContentType { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entries for Key"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + + NSArray *entriesData = @[ + @{@"uid": @"entry1", @"title": @"Entry 1"}, + @{@"uid": @"entry2", @"title": @"Entry 2"} + ]; + + [self.group configureWithDictionary:@{@"references": entriesData}]; + + // Get entries + NSArray *entries = [self.group entriesForKey:@"references" withContentType:@"source"]; + XCTAssertNotNil(entries, @"Entries should not be nil"); + XCTAssertEqual(entries.count, 2, @"Should have 2 entries"); + XCTAssertTrue([entries[0] isKindOfClass:[Entry class]]); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGroupEntriesForKeyWithNonArray { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entries for Non-Array"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + [self.group configureWithDictionary:@{@"references": @"notAnArray"}]; + + // Try to get entries for non-array + NSArray *entries = [self.group entriesForKey:@"references" withContentType:@"source"]; + XCTAssertNil(entries, @"Entries should be nil for non-array value"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGroupEntriesForNonExistentKey { + XCTestExpectation *expectation = [self expectationWithDescription:@"Entries for Non-Existent Key"]; + + self.group = [[Group alloc] initWithStack:self.stack]; + [self.group configureWithDictionary:@{@"key": @"value"}]; + + // Try to get entries for non-existent key + NSArray *entries = [self.group entriesForKey:@"nonExistent" withContentType:@"source"]; + XCTAssertNil(entries, @"Entries should be nil for non-existent key"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +@end + diff --git a/ContentstackTest/NSObjectExtensionsTest.m b/ContentstackTest/NSObjectExtensionsTest.m new file mode 100644 index 0000000..91d8100 --- /dev/null +++ b/ContentstackTest/NSObjectExtensionsTest.m @@ -0,0 +1,336 @@ +// +// NSObjectExtensionsTest.m +// ContentstackTest +// +// Created by Contentstack on 06/11/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import +#import "Stack.h" +#import "NSObject+Extensions.h" + +@interface NSObjectExtensionsTest : XCTestCase +@property (nonatomic, strong) NSObject *testObject; +@end + +@implementation NSObjectExtensionsTest + +- (void)setUp { + [super setUp]; + self.testObject = [[NSObject alloc] init]; +} + +- (void)tearDown { + self.testObject = nil; + [super tearDown]; +} + +#pragma mark - Network Error Codes Tests + +- (void)testNetworkErrorCodes { + NSArray *errorCodes = [self.testObject networkErrorCodes]; + + XCTAssertNotNil(errorCodes, @"Network error codes should not be nil"); + XCTAssertGreaterThan(errorCodes.count, 0, @"Should have error codes"); + + // Verify it contains expected error codes + XCTAssertTrue([errorCodes containsObject:@(kCFURLErrorNotConnectedToInternet)]); + XCTAssertTrue([errorCodes containsObject:@(kCFURLErrorCannotConnectToHost)]); +} + +- (void)testNetworkErrorCodesCaching { + NSArray *codes1 = [self.testObject networkErrorCodes]; + NSArray *codes2 = [self.testObject networkErrorCodes]; + + // Should return the same cached instance + XCTAssertEqual(codes1, codes2, @"Should return cached array"); +} + +#pragma mark - Host URLs Tests + +- (void)testHostURLS { + NSArray *urls = [self.testObject hostURLS]; + + XCTAssertNotNil(urls, @"Host URLs should not be nil"); + XCTAssertGreaterThan(urls.count, 0, @"Should have host URLs"); + XCTAssertTrue([urls containsObject:@"cdn.contentstack.io"]); + XCTAssertTrue([urls containsObject:@"eu-cdn.contentstack.com"]); +} + +- (void)testHostURLForUSRegion { + NSString *url = [self.testObject hostURL:0]; // US region + XCTAssertEqualObjects(url, @"cdn.contentstack.io"); +} + +- (void)testHostURLForEURegion { + NSString *url = [self.testObject hostURL:1]; // EU region + XCTAssertEqualObjects(url, @"eu-cdn.contentstack.com"); +} + +- (void)testHostURLForAURegion { + NSString *url = [self.testObject hostURL:2]; // AU region + XCTAssertEqualObjects(url, @"au-cdn.contentstack.com"); +} + +- (void)testHostURLForAzureNARegion { + NSString *url = [self.testObject hostURL:3]; // Azure NA + XCTAssertEqualObjects(url, @"azure-na-cdn.contentstack.com"); +} + +- (void)testHostURLForAzureEURegion { + NSString *url = [self.testObject hostURL:4]; // Azure EU + XCTAssertEqualObjects(url, @"azure-eu-cdn.contentstack.com"); +} + +- (void)testHostURLForGCPNARegion { + NSString *url = [self.testObject hostURL:5]; // GCP NA + XCTAssertEqualObjects(url, @"gcp-na-cdn.contentstack.com"); +} + +- (void)testHostURLForGCPEURegion { + NSString *url = [self.testObject hostURL:6]; // GCP EU + XCTAssertEqualObjects(url, @"gcp-eu-cdn.contentstack.com"); +} + +#pragma mark - Region Code Tests + +- (void)testRegionCodeUS { + NSString *code = [self.testObject regionCode:0]; + XCTAssertEqualObjects(code, @"us"); +} + +- (void)testRegionCodeEU { + NSString *code = [self.testObject regionCode:1]; + XCTAssertEqualObjects(code, @"eu"); +} + +- (void)testRegionCodeAU { + NSString *code = [self.testObject regionCode:2]; + XCTAssertEqualObjects(code, @"au"); +} + +- (void)testRegionCodeAzureNA { + NSString *code = [self.testObject regionCode:3]; + XCTAssertEqualObjects(code, @"azure-na"); +} + +- (void)testRegionCodeAzureEU { + NSString *code = [self.testObject regionCode:4]; + XCTAssertEqualObjects(code, @"azure-eu"); +} + +- (void)testRegionCodeGCPNA { + NSString *code = [self.testObject regionCode:5]; + XCTAssertEqualObjects(code, @"gcp-na"); +} + +- (void)testRegionCodeGCPEU { + NSString *code = [self.testObject regionCode:6]; + XCTAssertEqualObjects(code, @"gcp-eu"); +} + +#pragma mark - Locale Code Tests + +- (void)testLocaleCodeEnUS { + NSString *code = [self.testObject localeCode:0]; + XCTAssertEqualObjects(code, @"af-za", @"First locale should be af-za"); +} + +- (void)testLocaleCodeMultiple { + // Test a few different locales + NSString *code1 = [self.testObject localeCode:1]; + NSString *code2 = [self.testObject localeCode:2]; + + XCTAssertNotNil(code1); + XCTAssertNotNil(code2); + XCTAssertNotEqual(code1, code2, @"Different indices should return different locales"); +} + +- (void)testIndexOfLocaleCodeString { + NSUInteger index = [self.testObject indexOfLocaleCodeString:@"en-us"]; + XCTAssertEqual(index, 49, @"en-us should be at index 49"); +} + +- (void)testIndexOfLocaleCodeStringMultiple { + // Get a locale code and find its index + NSString *code = [self.testObject localeCode:5]; + NSUInteger index = [self.testObject indexOfLocaleCodeString:code]; + XCTAssertEqual(index, 5, @"Should find correct index"); +} + +- (void)testIndexOfInvalidLocaleCode { + NSUInteger index = [self.testObject indexOfLocaleCodeString:@"invalid-locale"]; + XCTAssertEqual(index, NSNotFound, @"Invalid locale should return NSNotFound"); +} + +#pragma mark - Publish Type Tests + +- (void)testPublishTypeAssetPublished { + NSString *type = [self.testObject publishType:0]; + XCTAssertEqualObjects(type, @"asset_published"); +} + +- (void)testPublishTypeEntryPublished { + NSString *type = [self.testObject publishType:1]; + XCTAssertEqualObjects(type, @"entry_published"); +} + +- (void)testPublishTypeAssetUnpublished { + NSString *type = [self.testObject publishType:2]; + XCTAssertEqualObjects(type, @"asset_unpublished"); +} + +- (void)testPublishTypeEntryUnpublished { + NSString *type = [self.testObject publishType:3]; + XCTAssertEqualObjects(type, @"entry_unpublished"); +} + +- (void)testPublishTypeAssetDeleted { + NSString *type = [self.testObject publishType:4]; + XCTAssertEqualObjects(type, @"asset_deleted"); +} + +- (void)testPublishTypeEntryDeleted { + NSString *type = [self.testObject publishType:5]; + XCTAssertEqualObjects(type, @"entry_deleted"); +} + +- (void)testPublishTypeContentTypeDeleted { + NSString *type = [self.testObject publishType:6]; + XCTAssertEqualObjects(type, @"content_type_deleted"); +} + +#pragma mark - JSON Conversion Tests + +- (void)testDictionaryFromJSONData { + NSDictionary *testDict = @{@"key": @"value", @"number": @123}; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:testDict options:0 error:nil]; + + NSDictionary *result = [self.testObject dictionaryFromJSONData:jsonData]; + + XCTAssertNotNil(result); + XCTAssertEqualObjects(result[@"key"], @"value"); + XCTAssertEqualObjects(result[@"number"], @123); +} + +- (void)testDictionaryFromInvalidJSONData { + NSData *invalidData = [@"invalid json" dataUsingEncoding:NSUTF8StringEncoding]; + NSDictionary *result = [self.testObject dictionaryFromJSONData:invalidData]; + + XCTAssertNil(result, @"Should return nil for invalid JSON"); +} + +- (void)testDictionaryFromNilData { + NSDictionary *result = [self.testObject dictionaryFromJSONData:nil]; + XCTAssertNil(result, @"Should return nil for nil data"); +} + +- (void)testJSONDataFromDictionary { + NSDictionary *testDict = @{@"key": @"value", @"number": @123}; + + NSData *jsonData = [self.testObject jsonDataFromDictonary:testDict]; + + XCTAssertNotNil(jsonData); + XCTAssertGreaterThan(jsonData.length, 0); + + // Verify it can be converted back + NSDictionary *parsedDict = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil]; + XCTAssertEqualObjects(parsedDict[@"key"], @"value"); +} + +- (void)testJSONStringFromDictionary { + NSDictionary *testDict = @{@"key": @"value"}; + + NSString *jsonString = [self.testObject jsonStringFromDictonary:testDict]; + + XCTAssertNotNil(jsonString); + XCTAssertTrue([jsonString containsString:@"key"]); + XCTAssertTrue([jsonString containsString:@"value"]); +} + +- (void)testArrayFromJSONData { + NSArray *testArray = @[@"item1", @"item2", @123]; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:testArray options:0 error:nil]; + + NSArray *result = [self.testObject arrayFromJSONData:jsonData]; + + XCTAssertNotNil(result); + XCTAssertEqual(result.count, 3); + XCTAssertEqualObjects(result[0], @"item1"); + XCTAssertEqualObjects(result[2], @123); +} + +- (void)testArrayFromInvalidJSONData { + NSData *invalidData = [@"invalid json" dataUsingEncoding:NSUTF8StringEncoding]; + NSArray *result = [self.testObject arrayFromJSONData:invalidData]; + + XCTAssertNil(result, @"Should return nil for invalid JSON"); +} + +- (void)testJSONStringFromArray { + NSArray *testArray = @[@"item1", @"item2", @123]; + + NSString *jsonString = [self.testObject jsonStringFromArray:testArray]; + + XCTAssertNotNil(jsonString); + XCTAssertTrue([jsonString containsString:@"item1"]); + XCTAssertTrue([jsonString containsString:@"item2"]); + XCTAssertTrue([jsonString containsString:@"123"]); +} + +#pragma mark - Property Type Assertion Tests + +- (void)testAssertPropertyTypesWithValidProperties { + NSDictionary *validProps = @{ + @"string": @"value", + @"number": @123, + @"null": [NSNull null], + @"array": @[@1, @2], + @"dict": @{@"key": @"value"}, + @"date": [NSDate date], + @"url": [NSURL URLWithString:@"https://example.com"] + }; + + // Should not throw + XCTAssertNoThrow([self.testObject assertPropertyTypes:validProps]); +} + +- (void)testAssertPropertyTypesWithEmptyDictionary { + NSDictionary *emptyDict = @{}; + XCTAssertNoThrow([self.testObject assertPropertyTypes:emptyDict]); +} + +#pragma mark - Perform and Wait Tests + +- (void)testPerformAndWait { + __block BOOL executed = NO; + + [NSObject performAndWait:^(dispatch_semaphore_t semaphore) { + // Simulate async work + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + executed = YES; + dispatch_semaphore_signal(semaphore); + }); + }]; + + XCTAssertTrue(executed, @"Block should have executed and waited"); +} + +- (void)testPerformAndWaitImmediateSignal { + __block BOOL executed = NO; + + [NSObject performAndWait:^(dispatch_semaphore_t semaphore) { + executed = YES; + dispatch_semaphore_signal(semaphore); + }]; + + XCTAssertTrue(executed, @"Block should have executed"); +} + +@end + + + + + diff --git a/ContentstackTest/QueryAdvancedTest.m b/ContentstackTest/QueryAdvancedTest.m new file mode 100644 index 0000000..f4ef0ce --- /dev/null +++ b/ContentstackTest/QueryAdvancedTest.m @@ -0,0 +1,361 @@ +// +// QueryAdvancedTest.m +// ContentstackTest +// +// Created by Contentstack on 06/11/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import +#import "Contentstack.h" +#import "Query.h" +#import "ContentType.h" +#import "Config.h" + +@interface QueryAdvancedTest : XCTestCase +@property (nonatomic, strong) Stack *stack; +@property (nonatomic, strong) Query *query; +@end + +@implementation QueryAdvancedTest + +- (void)setUp { + [super setUp]; + + NSString *filePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"config" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:filePath]; + if (data) { + NSDictionary *config = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + + Config *conf = [[Config alloc] init]; + conf.host = config[@"host"]; + + self.stack = [Contentstack stackWithAPIKey:config[@"api_key"] + accessToken:config[@"delivery_token"] + environmentName:config[@"environment"] + config:conf]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + self.query = [contentType query]; + } +} + +- (void)tearDown { + self.query = nil; + self.stack = nil; + [super tearDown]; +} + +#pragma mark - Language Tests + +- (void)testLanguageEnUS { + [self.query locale:@"US"]; + XCTAssertNotNil(self.query); +} + +- (void)testLanguageENGB { + [self.query locale:@"GB"]; + XCTAssertNotNil(self.query); +} + +- (void)testLanguageFRFR { + [self.query locale:@"FR"]; + XCTAssertNotNil(self.query); +} + +- (void)testLanguageDEDE { + [self.query locale:@"DE"]; + XCTAssertNotNil(self.query); +} + +- (void)testLanguageESES { + [self.query locale:@"ES"]; + XCTAssertNotNil(self.query); +} + +#pragma mark - Where Comparison Tests + +- (void)testWhereKeyEqualTo { + [self.query whereKey:@"title" equalTo:@"Test Title"]; + XCTAssertNotNil(self.query); +} + +- (void)testWhereKeyEqualToNumber { + [self.query whereKey:@"count" equalTo:@100]; + XCTAssertNotNil(self.query); +} + +- (void)testWhereKeyNotEqualTo { + [self.query whereKey:@"status" notEqualTo:@"draft"]; + XCTAssertNotNil(self.query); +} + +- (void)testWhereKeyLessThan { + [self.query whereKey:@"price" lessThan:@100]; + XCTAssertNotNil(self.query); +} + +- (void)testWhereKeyGreaterThan { + [self.query whereKey:@"views" greaterThan:@1000]; + XCTAssertNotNil(self.query); +} + +- (void)testWhereKeyLessThanOrEqualTo { + [self.query whereKey:@"rating" lessThanOrEqualTo:@5]; + XCTAssertNotNil(self.query); +} + +- (void)testWhereKeyGreaterThanOrEqualTo { + [self.query whereKey:@"age" greaterThanOrEqualTo:@18]; + XCTAssertNotNil(self.query); +} + +- (void)testWhereKeyContainedIn { + NSArray *values = @[@"published", @"archived"]; + [self.query whereKey:@"status" containedIn:values]; + XCTAssertNotNil(self.query); +} + +- (void)testWhereKeyContainedInEmptyArray { + [self.query whereKey:@"status" containedIn:@[]]; + XCTAssertNotNil(self.query); +} + +- (void)testWhereKeyNotContainedIn { + NSArray *values = @[@"draft", @"deleted"]; + [self.query whereKey:@"status" notContainedIn:values]; + XCTAssertNotNil(self.query); +} + +- (void)testWhereKeyNotContainedInMultiple { + NSArray *values = @[@"value1", @"value2", @"value3"]; + [self.query whereKey:@"field" notContainedIn:values]; + XCTAssertNotNil(self.query); +} + +#pragma mark - Reference Query Tests + +- (void)testWhereKeyNotIn { + ContentType *ct = [self.stack contentTypeWithName:@"reference"]; + Query *refQuery = [ct query]; + [refQuery whereKey:@"status" equalTo:@"published"]; + + [self.query whereKey:@"reference_field" notIn:refQuery]; + XCTAssertNotNil(self.query); +} + +#pragma mark - Include Reference Field Tests + +- (void)testIncludeReferenceFieldWithKeyArray { + NSArray *keys = @[@"reference1", @"reference2"]; + [self.query includeReferenceFieldWithKey:keys]; + XCTAssertNotNil(self.query); +} + +- (void)testIncludeReferenceFieldWithSingleKey { + [self.query includeReferenceFieldWithKey:@[@"single_reference"]]; + XCTAssertNotNil(self.query); +} + +- (void)testIncludeReferenceFieldWithKeyOnlyFields { + NSArray *fields = @[@"title", @"url"]; + [self.query includeReferenceFieldWithKey:@"reference_field" onlyFields:fields]; + XCTAssertNotNil(self.query); +} + +- (void)testIncludeReferenceFieldWithKeyOnlyFieldsSingle { + [self.query includeReferenceFieldWithKey:@"ref" onlyFields:@[@"title"]]; + XCTAssertNotNil(self.query); +} + +- (void)testIncludeReferenceFieldWithKeyExcludingFields { + NSArray *excludeFields = @[@"internal_field", @"metadata"]; + [self.query includeReferenceFieldWithKey:@"reference_field" excludingFields:excludeFields]; + XCTAssertNotNil(self.query); +} + +- (void)testIncludeReferenceFieldWithKeyExcludingMultiple { + NSArray *fields = @[@"field1", @"field2", @"field3"]; + [self.query includeReferenceFieldWithKey:@"ref" excludingFields:fields]; + XCTAssertNotNil(self.query); +} + +#pragma mark - Complex Query Tests + +- (void)testOrWithSubqueries { + ContentType *ct = [self.stack contentTypeWithName:@"source"]; + + Query *query1 = [ct query]; + [query1 whereKey:@"status" equalTo:@"published"]; + + Query *query2 = [ct query]; + [query2 whereKey:@"featured" equalTo:@YES]; + + [self.query orWithSubqueries:@[query1, query2]]; + XCTAssertNotNil(self.query); +} + +- (void)testOrWithSubqueriesMultiple { + ContentType *ct = [self.stack contentTypeWithName:@"source"]; + + Query *q1 = [ct query]; + [q1 whereKey:@"type" equalTo:@"article"]; + + Query *q2 = [ct query]; + [q2 whereKey:@"type" equalTo:@"blog"]; + + Query *q3 = [ct query]; + [q3 whereKey:@"type" equalTo:@"news"]; + + [self.query orWithSubqueries:@[q1, q2, q3]]; + XCTAssertNotNil(self.query); +} + +- (void)testAndWithSubqueries { + ContentType *ct = [self.stack contentTypeWithName:@"source"]; + + Query *query1 = [ct query]; + [query1 whereKey:@"status" equalTo:@"published"]; + + Query *query2 = [ct query]; + [query2 whereKey:@"featured" equalTo:@YES]; + + [self.query andWithSubqueries:@[query1, query2]]; + XCTAssertNotNil(self.query); +} + +- (void)testAndWithSubqueriesMultiple { + ContentType *ct = [self.stack contentTypeWithName:@"source"]; + + Query *q1 = [ct query]; + [q1 whereKey:@"published" equalTo:@YES]; + + Query *q2 = [ct query]; + [q2 whereKey:@"approved" equalTo:@YES]; + + Query *q3 = [ct query]; + [q3 whereKey:@"featured" equalTo:@YES]; + + [self.query andWithSubqueries:@[q1, q2, q3]]; + XCTAssertNotNil(self.query); +} + +#pragma mark - Additional Include Tests + +- (void)testIncludeOwner { + [self.query includeOwner]; + XCTAssertNotNil(self.query); +} + +- (void)testObjectsCount { + [self.query objectsCount]; + XCTAssertNotNil(self.query); +} + +#pragma mark - Query Dictionary Tests + +- (void)testQueryWithDictionary { + NSDictionary *queryDict = @{ + @"title": @"Test", + @"status": @"published" + }; + + [self.query query:queryDict]; + XCTAssertNotNil(self.query); +} + +- (void)testQueryWithEmptyDictionary { + [self.query query:@{}]; + XCTAssertNotNil(self.query); +} + +- (void)testQueryWithComplexDictionary { + NSDictionary *complexQuery = @{ + @"$or": @[ + @{@"status": @"published"}, + @{@"featured": @YES} + ] + }; + + [self.query query:complexQuery]; + XCTAssertNotNil(self.query); +} + +#pragma mark - Add Query Params Tests + +- (void)testAddQueryParams { + NSDictionary *params = @{ + @"key1": @"value1", + @"key2": @"value2" + }; + + [self.query addQueryParams:params]; + XCTAssertNotNil(self.query); +} + +- (void)testAddQueryParamsEmpty { + [self.query addQueryParams:@{}]; + XCTAssertNotNil(self.query); +} + +- (void)testAddQueryParamsMultiple { + [self.query addQueryParams:@{@"param1": @"val1"}]; + [self.query addQueryParams:@{@"param2": @"val2"}]; + [self.query addQueryParams:@{@"param3": @"val3"}]; + + XCTAssertNotNil(self.query); +} + +#pragma mark - Add Query With Key Tests + +- (void)testAddQueryWithKeyString { + [self.query addQueryWithKey:@"custom_key" andValue:@"custom_value"]; + XCTAssertNotNil(self.query); +} + +- (void)testAddQueryWithKeyNumber { + [self.query addQueryWithKey:@"count" andValue:@42]; + XCTAssertNotNil(self.query); +} + +- (void)testAddQueryWithKeyArray { + NSArray *array = @[@"item1", @"item2"]; + [self.query addQueryWithKey:@"items" andValue:array]; + XCTAssertNotNil(self.query); +} + +- (void)testAddQueryWithKeyDictionary { + NSDictionary *dict = @{@"nested": @"value"}; + [self.query addQueryWithKey:@"metadata" andValue:dict]; + XCTAssertNotNil(self.query); +} + +#pragma mark - Combined Query Tests + +- (void)testComplexQueryCombination { + [self.query whereKey:@"status" equalTo:@"published"]; + [self.query whereKey:@"views" greaterThan:@1000]; + [self.query orderByDescending:@"created_at"]; + [self.query limitObjects:@20]; + [self.query skipObjects:@10]; + [self.query includeCount]; + [self.query includeContentType]; + + XCTAssertNotNil(self.query); +} + +- (void)testMultipleWhereConditions { + [self.query whereKey:@"status" equalTo:@"published"]; + [self.query whereKey:@"featured" equalTo:@YES]; + [self.query whereKey:@"category" containedIn:@[@"tech", @"science"]]; + [self.query whereKey:@"rating" greaterThanOrEqualTo:@4]; + + XCTAssertNotNil(self.query); +} + +@end + + + + + diff --git a/ContentstackTest/QueryEdgeCaseTest.m b/ContentstackTest/QueryEdgeCaseTest.m new file mode 100644 index 0000000..c055fa8 --- /dev/null +++ b/ContentstackTest/QueryEdgeCaseTest.m @@ -0,0 +1,425 @@ +// +// QueryEdgeCaseTest.m +// ContentstackTest +// +// Created by Test Suite on 05/11/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import +#import +#import "CSIOInternalHeaders.h" +#import "ContentstackDefinitions.h" + +static NSInteger kRequestTimeOutInSeconds = 30; + +@interface QueryEdgeCaseTest : XCTestCase { + Stack *csStack; + Config *config; +} + +@property (nonatomic, strong) Stack *stack; + +@end + +@implementation QueryEdgeCaseTest + +- (void)setUp { + [super setUp]; + NSString *path = [[NSBundle bundleForClass:self.class] pathForResource:@"config" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:path]; + NSDictionary *configdict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; + config = [[Config alloc] init]; + config.host = configdict[@"host"]; + self.stack = [Contentstack stackWithAPIKey:configdict[@"api_key"] + accessToken:configdict[@"delivery_token"] + environmentName:configdict[@"environment"] + config:config]; +} + +- (void)waitForRequest { + [self waitForExpectationsWithTimeout:kRequestTimeOutInSeconds handler:^(NSError *error) { + if (error) { + XCTFail(@"Could not perform operation (Timed out) ~ ERR: %@", error.userInfo); + } + }]; +} + +- (void)tearDown { + self.stack = nil; + [super tearDown]; +} + +#pragma mark - Query Header Tests + +- (void)testQuerySetHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Set Header"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query setHeader:@"TestValue" forKey:@"X-Test-Header"]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testQueryAddHeadersWithDictionary { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Add Headers"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + NSDictionary *headers = @{@"Header-1": @"Value1", @"Header-2": @"Value2"}; + [query addHeadersWithDictionary:headers]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testQueryRemoveHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Remove Header"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query setHeader:@"TestValue" forKey:@"X-Test-Header"]; + [query removeHeaderForKey:@"X-Test-Header"]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Query Include Tests + +- (void)testQueryIncludeContentType { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Include Content Type"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query includeContentType]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testQueryIncludeReferenceContentTypeUid { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Include Ref Content Type UID"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query includeReferenceContentTypeUid]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testQueryIncludeEmbeddedItems { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Include Embedded Items"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query includeEmbeddedItems]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Query Field Selection + +- (void)testQueryOnlyFields { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Only Fields"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + NSArray *fields = @[@"title", @"url"]; + [query onlyFields:fields]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testQueryExceptFields { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Except Fields"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + NSArray *fields = @[@"internal_field"]; + [query exceptFields:fields]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Query Pagination + +- (void)testQueryLimit { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Limit"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query limitObjects:@10]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testQuerySkip { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Skip"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query skipObjects:@5]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Query Sorting + +- (void)testQueryAscendingOrder { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Ascending"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query orderByAscending:@"created_at"]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testQueryDescendingOrder { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Descending"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query orderByDescending:@"updated_at"]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Query Locale + +- (void)testQueryLocale { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Locale"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query locale:@"en-us"]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Query Include Count + +- (void)testQueryIncludeCount { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Include Count"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query includeCount]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Query Add Param + +- (void)testQueryAddParam { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Add Param"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query addParamKey:@"custom_param" andValue:@"custom_value"]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Query Remove Param + +- (void)testQueryRemoveParam { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Remove Param"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query addParamKey:@"test_param" andValue:@"test_value"]; + [query removeQueryWithKey:@"test_param"]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Query Where Tests + +- (void)testQueryWhereKeyExists { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Where Key Exists"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query whereKeyExists:@"title"]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +- (void)testQueryWhereKeyDoesNotExist { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Where Key Does Not Exist"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query whereKeyDoesNotExist:@"archived"]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Query Search + +- (void)testQuerySearch { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Search"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query search:@"test search"]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Query Regex + +- (void)testQueryWhereKeyMatchesRegex { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Regex Match"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query whereKey:@"title" matchesRegex:@"^Test.*" modifiers:@"i"]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Query Tags + +- (void)testQueryTags { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Tags"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + NSArray *tags = @[@"tag1", @"tag2"]; + [query tags:tags]; + XCTAssertNotNil(query); + + [expectation fulfill]; + [self waitForRequest]; +} + +#pragma mark - Query Fetch with Options + +- (void)testQueryFindWithAllOptions { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Find with All Options"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query includeContentType]; + [query includeCount]; + [query limitObjects:@5]; + [query skipObjects:@0]; + [query orderByAscending:@"created_at"]; + [query locale:@"en-us"]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (error) { + XCTFail(@"Error: %@", error); + } else { + XCTAssertNotNil(result); + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testQueryFindOneWithOptions { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Find One"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query includeContentType]; + + [query findOne:^(ResponseType type, Entry * _Nullable entry, NSError * _Nullable error) { + if (error) { + // May fail if no entries + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +#pragma mark - Query Cancel + +- (void)testQueryCancelRequests { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Cancel"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + // May or may not be called + }]; + + [query cancelRequests]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [expectation fulfill]; + }); + + [self waitForRequest]; +} + +@end + diff --git a/ContentstackTest/QueryResultAdvancedTest.m b/ContentstackTest/QueryResultAdvancedTest.m new file mode 100644 index 0000000..401bf4f --- /dev/null +++ b/ContentstackTest/QueryResultAdvancedTest.m @@ -0,0 +1,242 @@ +// +// QueryResultAdvancedTest.m +// ContentstackTest +// +// Created by Contentstack on 06/11/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import +#import "Contentstack.h" +#import "Query.h" +#import "QueryResult.h" +#import "ContentType.h" +#import "Config.h" + +@interface QueryResultAdvancedTest : XCTestCase +@property (nonatomic, strong) Stack *stack; +@end + +@implementation QueryResultAdvancedTest + +- (void)setUp { + [super setUp]; + + NSString *filePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"config" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:filePath]; + if (data) { + NSDictionary *config = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + + Config *conf = [[Config alloc] init]; + conf.host = config[@"host"]; + + self.stack = [Contentstack stackWithAPIKey:config[@"api_key"] + accessToken:config[@"delivery_token"] + environmentName:config[@"environment"] + config:conf]; + } +} + +- (void)tearDown { + self.stack = nil; + [super tearDown]; +} + +#pragma mark - Query Result Tests + +- (void)testQueryResultGetResult { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Result Get Result"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + [query limitObjects:@5]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (!error && result) { + NSArray *entries = [result getResult]; + XCTAssertNotNil(entries); + XCTAssertTrue([entries isKindOfClass:[NSArray class]]); + } + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + +- (void)testQueryResultTotalCount { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Result Total Count"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + [query includeCount]; + [query limitObjects:@5]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (!error && result) { + NSInteger count = [result totalCount]; + XCTAssertGreaterThanOrEqual(count, 0); + } + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + +- (void)testQueryResultSchema { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Result Schema"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + [query limitObjects:@1]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (!error && result) { + NSArray *schema = [result schema]; + // Schema may be nil if not requested + XCTAssertTrue(schema == nil || [schema isKindOfClass:[NSArray class]]); + } + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + +- (void)testQueryResultContentType { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Result Content Type"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + [query includeContentType]; + [query limitObjects:@1]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (!error && result) { + NSDictionary *ct = [result content_type]; + // Content type may be nil if not requested + XCTAssertTrue(ct == nil || [ct isKindOfClass:[NSDictionary class]]); + } + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + +- (void)testQueryResultWithEmptyResult { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Result Empty"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + // Query for something that doesn't exist + [query whereKey:@"uid" equalTo:@"nonexistent_uid_12345"]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (!error && result) { + NSArray *entries = [result getResult]; + XCTAssertNotNil(entries); + // May be empty array + XCTAssertTrue([entries isKindOfClass:[NSArray class]]); + } + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + +- (void)testQueryResultWithIncludeCount { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Result Include Count"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + [query includeCount]; + [query limitObjects:@10]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (!error && result) { + NSArray *entries = [result getResult]; + NSInteger count = [result totalCount]; + + XCTAssertNotNil(entries); + XCTAssertGreaterThanOrEqual(count, 0); + } + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + +- (void)testQueryResultMultipleCalls { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Result Multiple Calls"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + [query includeCount]; + [query limitObjects:@3]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (!error && result) { + // Call getResult multiple times - both should work + NSArray *entries1 = [result getResult]; + NSArray *entries2 = [result getResult]; + NSInteger count1 = [result totalCount]; + NSInteger count2 = [result totalCount]; + + XCTAssertNotNil(entries1, @"First call should return array"); + XCTAssertNotNil(entries2, @"Second call should return array"); + XCTAssertEqual(count1, count2, @"Multiple calls should return same count"); + } + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + +- (void)testQueryResultWithPagination { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Result Pagination"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + [query limitObjects:@5]; + [query skipObjects:@0]; + [query includeCount]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (!error && result) { + NSArray *entries = [result getResult]; + NSInteger count = [result totalCount]; + + XCTAssertNotNil(entries); + XCTAssertGreaterThanOrEqual(count, 0); + } + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + +- (void)testQueryResultWithContentTypeIncluded { + XCTestExpectation *expectation = [self expectationWithDescription:@"Query Result With Content Type"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + [query includeContentType]; + [query limitObjects:@1]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (!error && result) { + NSArray *entries = [result getResult]; + NSDictionary *ct = [result content_type]; + NSArray *schema = [result schema]; + + XCTAssertNotNil(entries); + // Content type and schema may or may not be present + XCTAssertTrue(ct == nil || [ct isKindOfClass:[NSDictionary class]]); + XCTAssertTrue(schema == nil || [schema isKindOfClass:[NSArray class]]); + } + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + +@end + diff --git a/ContentstackTest/QueryResultTest.m b/ContentstackTest/QueryResultTest.m new file mode 100644 index 0000000..6772250 --- /dev/null +++ b/ContentstackTest/QueryResultTest.m @@ -0,0 +1,208 @@ +// +// QueryResultTest.m +// ContentstackTest +// +// Created by Test Suite on 05/11/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import +#import +#import "CSIOInternalHeaders.h" +#import "ContentstackDefinitions.h" + +static NSInteger kRequestTimeOutInSeconds = 30; + +@interface QueryResultTest : XCTestCase { + Stack *csStack; + Config *config; +} + +@property (nonatomic, strong) Stack *stack; + +@end + +@implementation QueryResultTest + +- (void)setUp { + [super setUp]; + NSString *path = [[NSBundle bundleForClass:self.class] pathForResource:@"config" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:path]; + NSDictionary *configdict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; + config = [[Config alloc] init]; + config.host = configdict[@"host"]; + self.stack = [Contentstack stackWithAPIKey:configdict[@"api_key"] + accessToken:configdict[@"delivery_token"] + environmentName:configdict[@"environment"] + config:config]; +} + +- (void)waitForRequest { + [self waitForExpectationsWithTimeout:kRequestTimeOutInSeconds handler:^(NSError *error) { + if (error) { + XCTFail(@"Could not perform operation (Timed out) ~ ERR: %@", error.userInfo); + } + }]; +} + +- (void)tearDown { + self.stack = nil; + [super tearDown]; +} + +#pragma mark - QueryResult Tests + +- (void)testQueryResultGetResult { + XCTestExpectation *expectation = [self expectationWithDescription:@"QueryResult Get Result"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (error) { + XCTFail(@"Error: %@", error); + } else { + XCTAssertNotNil(result, @"Result should not be nil"); + + // Test getResult method + NSArray *entries = [result getResult]; + XCTAssertNotNil(entries, @"Entries should not be nil"); + XCTAssertTrue([entries isKindOfClass:[NSArray class]], @"Should be an array"); + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testQueryResultTotalCount { + XCTestExpectation *expectation = [self expectationWithDescription:@"QueryResult Total Count"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + [query includeCount]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (error) { + XCTFail(@"Error: %@", error); + } else { + XCTAssertNotNil(result); + + // Test totalCount method + NSInteger count = [result totalCount]; + XCTAssertGreaterThanOrEqual(count, 0, @"Count should be non-negative"); + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testQueryResultSchema { + XCTestExpectation *expectation = [self expectationWithDescription:@"QueryResult Schema"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (error) { + XCTFail(@"Error: %@", error); + } else { + XCTAssertNotNil(result); + + // Test schema method (may be nil if not included) + NSArray *schema = [result schema]; + if (schema) { + XCTAssertTrue([schema isKindOfClass:[NSArray class]], @"Schema should be an array"); + } + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testQueryResultContentType { + XCTestExpectation *expectation = [self expectationWithDescription:@"QueryResult Content Type"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + [query includeContentType]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (error) { + XCTFail(@"Error: %@", error); + } else { + XCTAssertNotNil(result); + + // Test content_type method + NSDictionary *ct = [result content_type]; + if (ct) { + XCTAssertTrue([ct isKindOfClass:[NSDictionary class]], @"Content type should be a dictionary"); + } + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testQueryResultWithAllIncludes { + XCTestExpectation *expectation = [self expectationWithDescription:@"QueryResult with All Includes"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + [query includeContentType]; + [query includeCount]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (error) { + XCTFail(@"Error: %@", error); + } else { + XCTAssertNotNil(result); + + // Test all methods + NSArray *entries = [result getResult]; + XCTAssertNotNil(entries); + + NSInteger count = [result totalCount]; + XCTAssertGreaterThanOrEqual(count, 0); + + NSArray *schema = [result schema]; + // Schema may be nil if not returned + + NSDictionary *ct = [result content_type]; + // Content type may be nil if not returned + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +- (void)testQueryResultWithLimit { + XCTestExpectation *expectation = [self expectationWithDescription:@"QueryResult with Limit"]; + + ContentType *contentType = [self.stack contentTypeWithName:@"source"]; + Query *query = [contentType query]; + [query limitObjects:@3]; + [query includeCount]; + + [query find:^(ResponseType type, QueryResult * _Nullable result, NSError * _Nullable error) { + if (error) { + XCTFail(@"Error: %@", error); + } else { + XCTAssertNotNil(result); + + NSArray *entries = [result getResult]; + XCTAssertNotNil(entries); + XCTAssertLessThanOrEqual(entries.count, 3, @"Should respect limit"); + } + [expectation fulfill]; + }]; + + [self waitForRequest]; +} + +@end + diff --git a/ContentstackTest/TaxonomyTest.m b/ContentstackTest/TaxonomyTest.m new file mode 100644 index 0000000..4ee047d --- /dev/null +++ b/ContentstackTest/TaxonomyTest.m @@ -0,0 +1,330 @@ +// +// TaxonomyTest.m +// ContentstackTest +// +// Created by Test Suite on 05/11/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import +#import +#import "CSIOInternalHeaders.h" +#import "Taxonomy.h" +#import "ContentstackDefinitions.h" + +@interface TaxonomyTest : XCTestCase { + Stack *csStack; + Config *config; +} + +@property (nonatomic, strong) Stack *stack; +@property (nonatomic, strong) Taxonomy *taxonomy; + +@end + +@implementation TaxonomyTest + +- (void)setUp { + [super setUp]; + NSString *path = [[NSBundle bundleForClass:self.class] pathForResource:@"config" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:path]; + NSDictionary *configdict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; + config = [[Config alloc] init]; + config.host = configdict[@"host"]; + self.stack = [Contentstack stackWithAPIKey:configdict[@"api_key"] + accessToken:configdict[@"delivery_token"] + environmentName:configdict[@"environment"] + config:config]; +} + +- (void)tearDown { + self.stack = nil; + self.taxonomy = nil; + [super tearDown]; +} + +#pragma mark - Header Tests + +- (void)testTaxonomySetHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"Set Header"]; + + // Get taxonomy from Stack + self.taxonomy = [self.stack taxonomy]; + + // Set header + [self.taxonomy setHeader:@"TestValue" forKey:@"Test-Header"]; + + // Verify header is set + NSDictionary *headers = [self.taxonomy valueForKey:@"headers"]; + XCTAssertNotNil(headers, @"Headers dictionary should not be nil"); + XCTAssertEqualObjects(headers[@"Test-Header"], @"TestValue", @"Header value should match"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testTaxonomyAddHeadersWithDictionary { + XCTestExpectation *expectation = [self expectationWithDescription:@"Add Headers Dictionary"]; + + self.taxonomy = [self.stack taxonomy]; + + // Add multiple headers + NSDictionary *headersToAdd = @{ + @"Header-One": @"Value1", + @"Header-Two": @"Value2", + @"Header-Three": @"Value3" + }; + + [self.taxonomy addHeadersWithDictionary:headersToAdd]; + + // Verify headers are added + NSDictionary *headers = [self.taxonomy valueForKey:@"headers"]; + XCTAssertNotNil(headers, @"Headers dictionary should not be nil"); + XCTAssertEqualObjects(headers[@"Header-One"], @"Value1", @"Header-One should match"); + XCTAssertEqualObjects(headers[@"Header-Two"], @"Value2", @"Header-Two should match"); + XCTAssertEqualObjects(headers[@"Header-Three"], @"Value3", @"Header-Three should match"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testTaxonomyRemoveHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"Remove Header"]; + + self.taxonomy = [self.stack taxonomy]; + + // Set header first + [self.taxonomy setHeader:@"TestValue" forKey:@"Test-Header"]; + + // Verify header is set + NSDictionary *headers = [self.taxonomy valueForKey:@"headers"]; + XCTAssertEqualObjects(headers[@"Test-Header"], @"TestValue", @"Header should be set"); + + // Remove header + [self.taxonomy removeHeaderForKey:@"Test-Header"]; + + // Verify header is removed + headers = [self.taxonomy valueForKey:@"headers"]; + XCTAssertNil(headers[@"Test-Header"], @"Header should be removed"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testTaxonomyRemoveNonExistentHeader { + XCTestExpectation *expectation = [self expectationWithDescription:@"Remove Non-Existent Header"]; + + self.taxonomy = [self.stack taxonomy]; + + // Try to remove header that doesn't exist (should not crash) + [self.taxonomy removeHeaderForKey:@"NonExistent-Header"]; + + // Verify no error + NSDictionary *headers = [self.taxonomy valueForKey:@"headers"]; + XCTAssertNotNil(headers, @"Headers dictionary should exist"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Query Tests + +- (void)testTaxonomyCreateQuery { + XCTestExpectation *expectation = [self expectationWithDescription:@"Create Query"]; + + self.taxonomy = [self.stack taxonomy]; + + // Create query from taxonomy + Query *query = [self.taxonomy query]; + + // Verify query is created + XCTAssertNotNil(query, @"Query should not be nil"); + XCTAssertTrue([query isKindOfClass:[Query class]], @"Query should be Query instance"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Fetch Tests + +- (void)testTaxonomyFetchWithParams { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch Taxonomy"]; + + self.taxonomy = [self.stack taxonomy]; + + // Fetch taxonomy with params + NSDictionary *params = @{ + @"taxonomies.taxonomy_uid": @"test_taxonomy" + }; + + [self.taxonomy fetch:params completion:^(NSDictionary * _Nullable entries, NSError * _Nullable error) { + // Note: This may fail if taxonomy doesn't exist in test environment + // But we're testing the method execution, not necessarily success + + // Either we get data or error, both are valid for testing + if (error) { + // Error is expected if taxonomy doesn't exist + XCTAssertNotNil(error, @"Error should be present if fetch fails"); + } else { + // Success case + XCTAssertNil(error, @"Error should be nil on success"); + } + + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30.0 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +- (void)testTaxonomyFetchWithNilParams { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch Taxonomy with Nil Params"]; + + self.taxonomy = [self.stack taxonomy]; + + // Fetch taxonomy with nil params + [self.taxonomy fetch:nil completion:^(NSDictionary * _Nullable entries, NSError * _Nullable error) { + // Either we get data or error, both are valid + if (error) { + XCTAssertNotNil(error, @"Error should be present if fetch fails"); + } else { + XCTAssertNil(error, @"Error should be nil on success"); + } + + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30.0 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +- (void)testTaxonomyFetchWithEmptyParams { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch Taxonomy with Empty Params"]; + + self.taxonomy = [self.stack taxonomy]; + + // Fetch taxonomy with empty params dictionary + NSDictionary *params = @{}; + + [self.taxonomy fetch:params completion:^(NSDictionary * _Nullable entries, NSError * _Nullable error) { + // Either we get data or error, both are valid + if (error) { + XCTAssertNotNil(error, @"Error should be present if fetch fails"); + } else { + XCTAssertNil(error, @"Error should be nil on success"); + } + + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30.0 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +- (void)testTaxonomyFetchWithMultipleParams { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch Taxonomy with Multiple Params"]; + + self.taxonomy = [self.stack taxonomy]; + + // Fetch taxonomy with multiple params + NSDictionary *params = @{ + @"taxonomies.taxonomy_uid": @"test_taxonomy", + @"limit": @10, + @"skip": @0 + }; + + [self.taxonomy fetch:params completion:^(NSDictionary * _Nullable entries, NSError * _Nullable error) { + // Test completes regardless of success/failure + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30.0 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +- (void)testTaxonomyFetchWithCustomHeaders { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch Taxonomy with Custom Headers"]; + + self.taxonomy = [self.stack taxonomy]; + + // Set custom headers + [self.taxonomy setHeader:@"CustomValue" forKey:@"X-Custom-Header"]; + + // Fetch taxonomy + NSDictionary *params = @{@"limit": @5}; + + [self.taxonomy fetch:params completion:^(NSDictionary * _Nullable entries, NSError * _Nullable error) { + // Test completes regardless of success/failure + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:30.0 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +#pragma mark - Integration Tests + +- (void)testTaxonomyQueryIntegration { + XCTestExpectation *expectation = [self expectationWithDescription:@"Taxonomy Query Integration"]; + + self.taxonomy = [self.stack taxonomy]; + + // Create query + Query *query = [self.taxonomy query]; + XCTAssertNotNil(query, @"Query should be created"); + + // Set headers on taxonomy + [self.taxonomy setHeader:@"TestValue" forKey:@"Test-Integration"]; + + // Verify taxonomy object is properly configured + NSDictionary *headers = [self.taxonomy valueForKey:@"headers"]; + XCTAssertEqualObjects(headers[@"Test-Integration"], @"TestValue"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testMultipleTaxonomyInstances { + XCTestExpectation *expectation = [self expectationWithDescription:@"Multiple Taxonomy Instances"]; + + // Create multiple taxonomy instances + Taxonomy *taxonomy1 = [self.stack taxonomy]; + Taxonomy *taxonomy2 = [self.stack taxonomy]; + + // Set different headers on each + [taxonomy1 setHeader:@"Value1" forKey:@"Test-Header"]; + [taxonomy2 setHeader:@"Value2" forKey:@"Test-Header"]; + + // Verify they're independent + NSDictionary *headers1 = [taxonomy1 valueForKey:@"headers"]; + NSDictionary *headers2 = [taxonomy2 valueForKey:@"headers"]; + + XCTAssertEqualObjects(headers1[@"Test-Header"], @"Value1"); + XCTAssertEqualObjects(headers2[@"Test-Header"], @"Value2"); + XCTAssertNotEqual(taxonomy1, taxonomy2, @"Instances should be different"); + + [expectation fulfill]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +@end + + + + + From c8ccd10a05d2465bb781a549056743cc31a56a67 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 14 Nov 2025 13:50:43 +0530 Subject: [PATCH 2/2] Update README.md to improve clarity in iOS SDK introduction. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 09dc4dd..cc72272 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Contentstack is a headless CMS with an API-first approach. It is a CMS that developers can use to build powerful cross-platform applications in their favorite languages. Build your application frontend, and Contentstack will take care of the rest. [Read More](https://www.contentstack.com/). -Contentstack provides iOS SDK to build application on top of iOS. Given below is the detailed guide and helpful resources to get started with our iOS SDK. +Contentstack provides iOS SDK to build application on top of iOS. Given below is the detailed guide and helpful resources to get started with our iOS SDK. ### Prerequisite