AzureAD / microsoft-authentication-library-for-objc

Microsoft Authentication Library (MSAL) for iOS and macOS
http://aka.ms/aadv2
MIT License
262 stars 142 forks source link

Merge main after 1.5.1 release back to dev #2348

Closed spetrescu84 closed 2 weeks ago

spetrescu84 commented 3 weeks ago

Proposed changes

Merge main after 1.5.1 release back to dev

Type of change

Risk

Additional information

ameyapat commented 3 weeks ago

Cloned PR for spetrescu/merge_main_1.5.1_to_dev


AI description (iteration 1)

PR Classification

Code cleanup

PR Summary

This pull request re-enables a previously commented-out unit test in the MSIDThrottlingServiceIntegrationTests.m file.

ameyapat commented 3 weeks ago

"To review the method testMSIDLRUCache_whenCallingAPIsUseThrottlingCacheWithinGCDBlocks_throttlingCacheShouldPerformOperationsWithThreadSafety, let's break it down step-by-step:

Logical and Syntactical Bugs

  1. Error Handling: The subError variable is shared across multiple threads without synchronization, which can lead to race conditions.
  2. Assertions: Assertions inside the GCD blocks might not be executed before the test method completes, leading to false positives or negatives.
  3. Expectations: The expectations are not properly waited for before the assertions are made.

Suggested Fixes

  1. Error Handling: Use a thread-safe mechanism to handle errors.
  2. Assertions: Ensure assertions are executed within the main thread or properly synchronized.
  3. Expectations: Ensure all expectations are fulfilled and waited for before making assertions.

Refactoring for Readability

  1. Separate Concerns: Break down the method into smaller helper methods for adding and removing cache records.
  2. Use Dispatch Groups: Use dispatch groups to synchronize the completion of concurrent tasks.

Refactored Code

- (void)testMSIDLRUCache_whenCallingAPIsUseThrottlingCacheWithinGCDBlocks_throttlingCacheShouldPerformOperationsWithThreadSafety
{
    dispatch_queue_t parentQ1 = dispatch_queue_create(\"parentQ1\", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t parentQ2 = dispatch_queue_create(\"parentQ2\", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();

    XCTestExpectation *expectation1 = [[XCTestExpectation alloc] initWithDescription:@\"Calling API1\"];
    XCTestExpectation *expectation2 = [[XCTestExpectation alloc] initWithDescription:@\"Calling API2\"];
    XCTestExpectation *expectation3 = [[XCTestExpectation alloc] initWithDescription:@\"Calling API3\"];
    XCTestExpectation *expectation4 = [[XCTestExpectation alloc] initWithDescription:@\"Calling API4\"];

    NSArray *expectationsAdd = @[expectation1, expectation2];
    NSArray *expectationsRemove = @[expectation3, expectation4];

    MSIDLRUCache *customLRUCache = [[MSIDLRUCache alloc] initWithCacheSize:100];
    __block NSError *subError = nil;
    __block NSError *threadSafeError = nil;

    dispatch_group_async(group, parentQ1, ^{
        for (int i = 0; i < 50; i++)
        {
            NSString *cacheKey = [NSString stringWithFormat:@\"%i\", i];
            MSIDThrottlingCacheRecord *throttleCacheRecord = [[MSIDThrottlingCacheRecord alloc] initWithErrorResponse:nil
                                                                                                         throttleType:i
                                                                                                     throttleDuration:100];
            [customLRUCache setObject:throttleCacheRecord
                               forKey:cacheKey
                                error:&subError];
            if (subError) {
                @synchronized (self) {
                    threadSafeError = subError;
                }
            }
        }
        [expectation1 fulfill];
    });

    dispatch_group_async(group, parentQ2, ^{
        for (int i = 50; i < 100; i++)
        {
            NSString *cacheKey = [NSString stringWithFormat:@\"%i\", i];
            MSIDThrottlingCacheRecord *throttleCacheRecord = [[MSIDThrottlingCacheRecord alloc] initWithErrorResponse:nil
                                                                                                         throttleType:i
                                                                                                     throttleDuration:100];
            [customLRUCache setObject:throttleCacheRecord
                               forKey:cacheKey
                                error:&subError];
            if (subError) {
                @synchronized (self) {
                    threadSafeError = subError;
                }
            }
        }
        [expectation2 fulfill];
    });

    [self waitForExpectations:expectationsAdd timeout:20];
    XCTAssertEqual(customLRUCache.numCacheRecords, 100);

    dispatch_group_async(group, parentQ1, ^{
        for (int i = 0; i < 50; i++)
        {
            NSString *cacheKey = [NSString stringWithFormat:@\"%i\", i];
            NSString *cacheKeyFromOtherQueue = [NSString stringWithFormat:@\"%i\", (i + 50)];
            if (i % 2)
            {
                [customLRUCache removeObjectForKey:cacheKey error:&subError];
                if (subError) {
                    @synchronized (self) {
                        threadSafeError = subError;
                    }
                }

                MSIDThrottlingCacheRecord *throttleCacheRecord = [[MSIDThrottlingCacheRecord alloc] initWithErrorResponse:nil
                                                                                                             throttleType:(i + 50)
                                                                                                         throttleDuration:100];
                [customLRUCache setObject:throttleCacheRecord forKey:cacheKeyFromOtherQueue error:&subError];
                if (subError) {
                    @synchronized (self) {
                        threadSafeError = subError;
                    }
                }
            }
        }
        [expectation3 fulfill];
    });

    dispatch_group_async(group, parentQ2, ^{
        for (int i = 50; i < 100; i++)
        {
            NSString *cacheKey = [NSString stringWithFormat:@\"%i\", i];
            NSString *cacheKeyFromOtherQueue = [NSString stringWithFormat:@\"%i\", (i - 50)];
            if (i % 2)
            {
                [customLRUCache removeObjectForKey:cacheKey error:&subError];
                if (subError) {
                    @synchronized (self) {
                        threadSafeError = subError;
                    }
                }

                MSIDThrottlingCacheRecord *throttleCacheRecord = [[MSIDThrottlingCacheRecord alloc] initWithErrorResponse:nil
                                                                                                             throttleType:(i - 50)
                                                                                                         throttleDuration:100];
                [customLRUCache setObject:throttleCacheRecord forKey:cacheKeyFromOtherQueue error:&subError];
                if (subError) {
                    @synchronized (self) {
                        threadSafeError = subError;
                    }
                }
            }
        }
        [expectation4 fulfill];
    });

    [self waitForExpectations:expectationsRemove timeout:20];

    XCTAssertEqual(customLRUCache.numCacheRecords, customLRUCache.cacheAddCount - customLRUCache.cacheRemoveCount);
    XCTAssertNil(threadSafeError);
}

This refactored code ensures thread safety, proper error handling, and better readability. AI-generated content may be incorrect

ameyapat commented 3 weeks ago

"The code change snippet indicates the removal of the method testMSIDThrottlingServiceIntegration_SSOSilentRequestThatReturnsInteractionRequiredError_ShouldBeThrottledSuccessfully_AndThenUnThrottledUponLaterSuccessfulInteractionRequest. Since the method is removed, there is no need to review it for logical or syntactical bugs or suggest refactoring.

However, if you want to review the entire file for potential improvements, here are some general suggestions:

  1. Refactor Long Methods: Some methods are quite long and could be broken down into smaller, more manageable methods. For example, the testMSIDThrottlingServiceIntegration_NonSSOSilentRequestThatReturns429Response_ShouldBeThrottledByThrottlingService method could be split into smaller methods for setting up the test, making the request, and verifying the results.

  2. Use Constants for Repeated Strings: Strings like @\"429\", @\"Retry-After\", and @\"100\" are repeated multiple times. Consider defining them as constants to avoid magic numbers and strings.

  3. Remove Unused Imports: Ensure that all imported headers are necessary. Removing unused imports can make the code cleaner and reduce compilation time.

  4. Consistent Error Handling: Ensure that error handling is consistent across all methods. For example, some methods check for errors and handle them, while others do not.

  5. Use Helper Methods for Common Logic: If there is common logic used across multiple tests, consider extracting it into helper methods. This can reduce code duplication and make the tests easier to maintain.

  6. Add Comments for Complex Logic: Adding comments to explain complex logic can make the code easier to understand for future maintainers.

Here is an example of how you might refactor one of the long test methods:

Refactored Example

- (void)testMSIDThrottlingServiceIntegration_NonSSOSilentRequestThatReturns429Response_ShouldBeThrottledByThrottlingService
{
    MSIDDefaultSilentTokenRequest *defaultSilentTokenRequest = [self createDefaultSilentTokenRequest];
    MSIDRefreshToken *refreshToken = [self createRefreshToken];
    MSIDThrottlingServiceMock *throttlingServiceMock = [self createThrottlingServiceMock];
    defaultSilentTokenRequest.throttlingService = throttlingServiceMock;

    [self swizzleTokenEndpoint];
    MSIDTestURLResponse *tokenResponse = [self createTokenResponseWithCode:429 retryAfter:@\"100\"];

    [self performSilentRequest:defaultSilentTokenRequest withRefreshToken:refreshToken expectedThrottleCount:0 expectedUpdateCount:1];
    [self verifyThrottlingCacheForKey:@\"9671032187006166342\" expectedThrottleType:MSIDThrottlingType429];

    [self performSilentRequest:defaultSilentTokenRequest withRefreshToken:refreshToken expectedThrottleCount:1 expectedUpdateCount:1];
}

- (MSIDDefaultSilentTokenRequest *)createDefaultSilentTokenRequest
{
    return [[MSIDDefaultSilentTokenRequest alloc] initWithRequestParameters:self.silentRequestParameters
                                                               forceRefresh:NO
                                                               oauthFactory:[MSIDAADV2Oauth2Factory new]
                                                     tokenResponseValidator:[MSIDDefaultTokenResponseValidator new]
                                                                 tokenCache:self.tokenCache
                                                       accountMetadataCache:self.accountMetadataCache];
}

- (MSIDRefreshToken *)createRefreshToken
{
    MSIDRefreshToken *refreshToken = [[MSIDRefreshToken alloc] init];
    refreshToken.refreshToken = self.refreshToken;
    return refreshToken;
}

- (MSIDThrottlingServiceMock *)createThrottlingServiceMock
{
    return [[MSIDThrottlingServiceMock alloc] initWithDataSource:self.keychainTokenCache context:self.silentRequestParameters];
}

- (void)swizzleTokenEndpoint
{
    [MSIDTestSwizzle instanceMethod:@selector(tokenEndpoint)
                              class:[MSIDRequestParameters class]
                              block:(id)^(void) {
        return [[NSURL alloc] initWithString:DEFAULT_TEST_TOKEN_ENDPOINT_GUID];
    }];
}

- (MSIDTestURLResponse *)createTokenResponseWithCode:(NSInteger)code retryAfter:(NSString *)retryAfter
{
    MSIDTestURLResponse *tokenResponse = [MSIDTestURLResponse refreshTokenGrantResponseForThrottling:self.refreshToken
                                                                                       requestClaims:self.atRequestClaim
                                                                                       requestScopes:self.oidcScopeString
                                                                                          responseAT:@\"new at\"
                                                                                          responseRT:self.refreshToken
                                                                                          responseID:nil
                                                                                       responseScope:@\"user.read tasks.read\"
                                                                                  responseClientInfo:nil
                                                                                                 url:DEFAULT_TEST_TOKEN_ENDPOINT_GUID
                                                                                        responseCode:code
                                                                                           expiresIn:nil
                                                                                        enrollmentId:@\"adf79e3f-mike-454d-9f0f-2299e76dbfd5\"
                                                                                         redirectUri:self.redirectUri
                                                                                            clientId:self.silentRequestParameters.clientId];
    tokenResponse->_error = MSIDCreateError(MSIDErrorDomain, MSIDErrorInternal, @\"429 error test\", @\"oAuthError\", @\"subError\", nil, nil, @{MSIDHTTPResponseCodeKey : @(code).stringValue, MSIDHTTPHeadersKey: @{@\"Retry-After\": retryAfter}}, NO);
    return tokenResponse;
}

- (void)performSilentRequest:(MSIDDefaultSilentTokenRequest *)request withRefreshToken:(MSIDRefreshToken *)refreshToken expectedThrottleCount:(NSUInteger)throttleCount expectedUpdateCount:(NSUInteger)updateCount
{
    XCTestExpectation *expectation = [self expectationWithDescription:@\"silent request\"];
    [request acquireTokenWithRefreshTokenImpl:refreshToken completionBlock:^(MSIDTokenResult * _Nullable result, NSError * _Nullable error) {
        XCTAssertEqual(request.throttlingService.shouldThrottleRequestInvokedCount, throttleCount);
        XCTAssertEqual(request.throttlingService.updateThrottlingServiceInvokedCount, updateCount);
        XCTAssertNil(result);
        XCTAssertNotNil(error);
        XCTAssertEqual(error.code, MSIDErrorInternal);
        [expectation fulfill];
    }];
    [self waitForExpectationsWithTimeout:5.0 handler:nil];
}

- (void)verifyThrottlingCacheForKey:(NSString *)key expectedThrottleType:(MSIDThrottlingType)throttleType
{
    NSError *subError = nil;
    MSIDThrottlingCacheRecord *record = [[MSIDLRUCache sharedInstance] objectForKey:key error:&subError];
    XCTAssertNotNil(record);
    XCTAssertNil(subError);
    XCTAssertEqual(record.throttleType, throttleType);
    XCTAssertEqual(record.cachedErrorResponse.code, MSIDErrorInternal);
    XCTAssertEqual(record.cachedErrorResponse.domain, MSIDErrorDomain);
    XCTAssertEqual(record.throttledCount, 1);
}

This refactoring breaks down the long test method into smaller, more manageable methods, making the code easier to read and maintain. AI-generated content may be incorrect

ameyapat commented 3 weeks ago

"### Review and Suggestions

The code snippet provided is a single line:

sequence:nil];

This line is part of a method call, specifically:

msidParams.platformSequence = [NSString msidUpdatePlatformSequenceParamWithSrcName:[MSIDVersion platformName]
                                                                        srcVersion:[MSIDVersion sdkVersion]
                                                                          sequence:nil];

Logical and Syntactical Review

  1. Logical Review:

    • The method msidUpdatePlatformSequenceParamWithSrcName:srcVersion:sequence: is being called with sequence:nil. This implies that the sequence parameter is being set to nil. This might be intentional, but it is important to ensure that the method can handle a nil value for this parameter without causing any issues.
  2. Syntactical Review:

    • There are no syntactical errors in this line. The method call is correctly formatted.

Refactoring Suggestions

To improve readability and maintainability, consider the following refactoring suggestions:

  1. Extract Method Call into a Variable:

    • Extract the method call into a variable to make the code more readable and to provide context for the nil value.
  2. Add Comments:

    • Add comments to explain why nil is being passed to the sequence parameter.

Here is the refactored code with these suggestions:

// Update platform sequence parameter with source name, version, and sequence
NSString *platformSequence = [NSString msidUpdatePlatformSequenceParamWithSrcName:[MSIDVersion platformName]
                                                                       srcVersion:[MSIDVersion sdkVersion]
                                                                         sequence:nil]; // sequence is set to nil intentionally

msidParams.platformSequence = platformSequence;

Summary

ameyapat commented 3 weeks ago

"### Plan

  1. Review the jobs section for logical and syntactical errors.
  2. Identify any potential improvements for readability and maintainability.
  3. Suggest fixes and refactoring.

Review and Suggestions

Logical and Syntactical Bugs

  1. Job Validate_Pull_Request:

    • maxParallel should be maxParallelism.
    • timeOutInMinutes should be timeoutInMinutes.
  2. Job fetch_pr_labels:

    • No major issues found.
  3. Job e2e_test_native_auth_ios and e2e_test_native_auth_mac:

    • No major issues found.
  4. Job Validate_SPM_Integration:

    • timeOutInMinutes should be timeoutInMinutes.

Refactoring for Readability

  1. Use consistent indentation and spacing.
  2. Group related steps together and add comments for clarity.
  3. Extract repeated scripts into templates or reusable steps if possible.

Refactored Code

jobs:
- job: 'Validate_Pull_Request'
  strategy:
    maxParallelism: 3
    matrix:
      IOS_FRAMEWORK: 
        target: \"iosFramework iosTestApp sampleIosApp sampleIosAppSwift\"
      MAC_FRAMEWORK: 
        target: \"macFramework\"
      VISION_FRAMEWORK:
        target: \"visionOSFramework\"
  displayName: Validate Pull Request
  pool:
    vmImage: 'macOS-14'
    timeoutInMinutes: 30

  steps:
  - script: |
      /bin/bash -c \"sudo xcode-select -s /Applications/Xcode_15.4.app\"
    displayName: 'Switch to use Xcode 15.4'
  - task: CmdLine@2
    displayName: Installing dependencies
    inputs:
      script: |
        gem install xcpretty slather bundler -N
      failOnStderr: true
  - checkout: self
    clean: true
    submodules: true
    fetchDepth: 1
    persistCredentials: false
  - task: Bash@3
    displayName: Removing any lingering codecov files
    inputs:
      targetType: 'inline'
      script: |
        find . -name \"*.gcda\" -print0 | xargs -0 rm
  - task: ComponentGovernanceComponentDetection@0
    inputs:
      alertWarningLevel: Low
  - task: Bash@3
    displayName: download visionOS SDK
    inputs:
      targetType: 'inline'
      script: |
        echo $(target)
        if [ $(target) == 'visionOSFramework' ]; then
            echo \"Downloading simulator for visionOS\"
            sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer
            defaults write com.apple.dt.Xcode AllowUnsupportedVisionOSHost -bool YES
            defaults write com.apple.CoreSimulator AllowUnsupportedVisionOSHost -bool YES
            xcodebuild -downloadPlatform visionOS
        else
            echo \"Not visionOS job, no download needed\"
        fi
      failOnStderr: false
  - task: Bash@3
    displayName: Run Build script & check for Errors
    inputs:
      targetType: 'inline'
      script: |
        { output=$(./build.py --target $(target) 2>&1 1>&3-) ;} 3>&1
        final_status=$(<./build/status.txt)
        echo \"FINAL STATUS  = ${final_status}\"
        echo \"POSSIBLE ERRORS: ${output}\"

        if [ $final_status != \"0\" ]; then
          echo \"Build & Testing Failed! 
 ${output}\" >&2
        fi
      failOnStderr: true
  - task: Bash@3
    condition: always()
    displayName: Cleanup
    inputs:
      targetType: 'inline'
      script: |
        rm -rf ./build/status.txt
  - task: PublishTestResults@2
    condition: always()
    displayName: Publish Test Report
    inputs:
      testResultsFormat: 'JUnit'
      testResultsFiles: '$(Agent.BuildDirectory)/s/build/reports/*'
      failTaskOnFailedTests: true
      testRunTitle: 'Test Run - $(target)'

- job: fetch_pr_labels
  displayName: 'Check for PR Label'
  timeoutInMinutes: 5
  pool:
    vmImage: 'macOS-14'
  steps:
  - script: |
      url=\"https://api.github.com/repos/$BUILD_REPOSITORY_ID/issues/$SYSTEM_PULLREQUEST_PULLREQUESTNUMBER/labels\"

      echo \"Fetching labels from $url \"

      temp_file=$(mktemp)
      response_code=$(curl -s -w \"%{http_code}\" -o \"$temp_file\" \"$url\")
      response_content=$(cat \"$temp_file\")

      echo \"Response code: $response_code\"
      echo \"Raw response: $response_content\"

      if [[ \"$response_code\" -eq 200 ]]; then
        label_names=$(echo $response_content | jq -r '.[].name' | paste -sd ', ' -)
        echo \"##vso[task.setvariable variable=PR_LABELS;isOutput=true]$label_names\"
        [ -z \"$label_names\" ] && echo \"PR labels: \" || echo \"PR labels: $label_names\"
      else
        echo \"Request failed with status code: $response_code - Skipping Native Auth E2E tests as a preventive measure\"
        echo \"##vso[task.setvariable variable=PR_LABELS;isOutput=true]'skip-native-auth-e2e-tests'\"
      fi
    name: fetchPrLabels

- job: e2e_test_native_auth_ios
  displayName: 'Run MSAL E2E tests for iOS native auth'
  dependsOn: fetch_pr_labels
  condition: and(succeeded(), not(contains(dependencies.fetch_pr_labels.outputs['fetchPrLabels.PR_LABELS'], 'skip-native-auth-e2e-tests')))
  timeoutInMinutes: 30
  cancelTimeoutInMinutes: 5
  pool:
    vmImage: 'macOS-14'
  workspace:
    clean: all

  steps:
  - template: templates/tests-with-conf-file.yml
    parameters:
      schema: 'MSAL iOS Native Auth E2E Tests'
      full_path: 'build/Build/Products/MSAL iOS Native Auth E2E Tests_MSAL iOS Native Auth E2E Tests_iphonesimulator17.5-x86_64.xctestrun'
      destination: 'platform=iOS Simulator,name=iPhone 15,OS=17.5'
      sdk: 'iphonesimulator'

- job: e2e_test_native_auth_mac
  displayName: 'Run MSAL E2E tests for macOS native auth'
  dependsOn: fetch_pr_labels
  condition: and(succeeded(), not(contains(dependencies.fetch_pr_labels.outputs['fetchPrLabels.PR_LABELS'], 'skip-native-auth-e2e-tests')))
  timeoutInMinutes: 30
  cancelTimeoutInMinutes: 5
  pool:
    vmImage: 'macOS-14'
  workspace:
    clean: all

  steps:
  - template: templates/tests-with-conf-file.yml
    parameters:
      schema: 'MSAL Mac Native Auth E2E Tests'
      full_path: 'build/Build/Products/MSAL Mac Native Auth E2E Tests_MSAL Mac Native Auth E2E Tests_macosx14.5-x86_64.xctestrun'
      destination: 'platform=macOS'
      sdk: 'macosx'

- job: 'Validate_SPM_Integration'
  displayName: Validate SPM Integration
  pool:
    vmImage: 'macOS-13'
    timeoutInMinutes: 15
  workspace:
    clean: all

  steps:
  - checkout: self
    clean: true
    submodules: true
    fetchDepth: 1
    persistCredentials: true
    path: s
  - script: |
      /bin/bash -c \"sudo xcode-select -s /Applications/Xcode_14.3.app\"
    displayName: 'Switch to use Xcode 14.3'
  - task: Bash@3
    displayName: Set variable BRANCH_NAME to a temporary branch
    inputs:
      targetType: 'inline'
      script: |
        BRANCH_NAME_LOCAL=\"$(Build.SourceBranchName)-temp\"
        echo \"##vso[task.setvariable variable=BRANCH_NAME]${BRANCH_NAME_LOCAL}\"
  - task: Bash@3
    displayName: Checkout to temporary branch
    inputs:
      targetType: 'inline'
      script: |
        git checkout -b \"${BRANCH_NAME}\"
  - task: Bash@3
    displayName: Run SPM integration test script
    inputs:
      targetType: 'inline'
      script: |
        sh spm-integration-test.sh \"${BRANCH_NAME}\"
    continueOnError: false
  - task: Bash@3
    condition: always()
    displayName: Cleanup
    inputs:
      targetType: 'inline'
      script: |
        cd ../..
        rm -rf \"$SAMPLE_APP_TEMP_DIR\" archive framework MSAL.zip
        git checkout -- .
        git fetch --quiet
        git switch \"$(Build.SourceBranchName)\"
        git branch -D \"$BRANCH_NAME\"
        git push origin --delete \"$BRANCH_NAME\"

Summary