Back to Repositories

Testing Offline DRM License Management in SmartTube

This test suite validates the OfflineLicenseHelper class functionality for DRM license management in ExoPlayer. It focuses on testing offline license downloading, renewal, and release operations while ensuring proper key set handling and duration tracking.

Test Coverage Overview

The test suite provides comprehensive coverage of offline DRM license operations including:

  • License downloading and key set validation
  • License renewal workflows
  • License release procedures
  • Duration tracking for licenses and playback
  • Error handling for invalid initialization data

Implementation Analysis

The testing approach utilizes JUnit and Mockito frameworks to validate DRM functionality. Mock objects simulate MediaDrm and callback behaviors, while Robolectric provides Android runtime simulation. The tests verify both successful operations and error cases through systematic validation of license operations.

Technical Details

Key technical components include:

  • AndroidJUnit4 test runner
  • Robolectric for Android environment simulation
  • Mockito for DRM component mocking
  • Custom shadow classes for Looper and MessageQueue
  • Widevine DRM configuration

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Proper test setup and teardown
  • Systematic mock configuration
  • Comprehensive error case coverage
  • Clear test method naming
  • Isolated test scenarios

yuliskov/smarttube

exoplayer-amzn-2.10.6/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java

            
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.android.exoplayer2.drm;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;

import android.util.Pair;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.testutil.RobolectricUtil;
import java.util.HashMap;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;

/** Tests {@link OfflineLicenseHelper}. */
@RunWith(AndroidJUnit4.class)
@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class})
public class OfflineLicenseHelperTest {

  private OfflineLicenseHelper<?> offlineLicenseHelper;
  @Mock private MediaDrmCallback mediaDrmCallback;
  @Mock private ExoMediaDrm<ExoMediaCrypto> mediaDrm;

  @Before
  public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
    when(mediaDrm.openSession()).thenReturn(new byte[] {1, 2, 3});
    offlineLicenseHelper =
        new OfflineLicenseHelper<>(C.WIDEVINE_UUID, mediaDrm, mediaDrmCallback, null);
  }

  @After
  public void tearDown() throws Exception {
    offlineLicenseHelper.release();
    offlineLicenseHelper = null;
  }

  @Test
  public void testDownloadRenewReleaseKey() throws Exception {
    setStubLicenseAndPlaybackDurationValues(1000, 200);

    byte[] keySetId = {2, 5, 8};
    setStubKeySetId(keySetId);

    byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData());

    assertOfflineLicenseKeySetIdEqual(keySetId, offlineLicenseKeySetId);

    byte[] keySetId2 = {6, 7, 0, 1, 4};
    setStubKeySetId(keySetId2);

    byte[] offlineLicenseKeySetId2 = offlineLicenseHelper.renewLicense(offlineLicenseKeySetId);

    assertOfflineLicenseKeySetIdEqual(keySetId2, offlineLicenseKeySetId2);

    offlineLicenseHelper.releaseLicense(offlineLicenseKeySetId2);
  }

  @Test
  public void testDownloadLicenseFailsIfNullInitData() throws Exception {
    try {
      offlineLicenseHelper.downloadLicense(null);
      fail();
    } catch (IllegalArgumentException e) {
      // Expected.
    }
  }

  @Test
  public void testDownloadLicenseFailsIfNoKeySetIdIsReturned() throws Exception {
    setStubLicenseAndPlaybackDurationValues(1000, 200);

    try {
      offlineLicenseHelper.downloadLicense(newDrmInitData());
      fail();
    } catch (Exception e) {
      // Expected.
    }
  }

  @Test
  public void testDownloadLicenseDoesNotFailIfDurationNotAvailable() throws Exception {
    setDefaultStubKeySetId();

    byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData());

    assertThat(offlineLicenseKeySetId).isNotNull();
  }

  @Test
  public void testGetLicenseDurationRemainingSec() throws Exception {
    long licenseDuration = 1000;
    int playbackDuration = 200;
    setStubLicenseAndPlaybackDurationValues(licenseDuration, playbackDuration);
    setDefaultStubKeySetId();

    byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData());

    Pair<Long, Long> licenseDurationRemainingSec =
        offlineLicenseHelper.getLicenseDurationRemainingSec(offlineLicenseKeySetId);

    assertThat(licenseDurationRemainingSec.first).isEqualTo(licenseDuration);
    assertThat(licenseDurationRemainingSec.second).isEqualTo(playbackDuration);
  }

  @Test
  public void testGetLicenseDurationRemainingSecExpiredLicense() throws Exception {
    long licenseDuration = 0;
    int playbackDuration = 0;
    setStubLicenseAndPlaybackDurationValues(licenseDuration, playbackDuration);
    setDefaultStubKeySetId();

    byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData());

    Pair<Long, Long> licenseDurationRemainingSec =
        offlineLicenseHelper.getLicenseDurationRemainingSec(offlineLicenseKeySetId);

    assertThat(licenseDurationRemainingSec.first).isEqualTo(licenseDuration);
    assertThat(licenseDurationRemainingSec.second).isEqualTo(playbackDuration);
  }

  private void setDefaultStubKeySetId()
      throws android.media.NotProvisionedException, android.media.DeniedByServerException {
    setStubKeySetId(new byte[] {2, 5, 8});
  }

  private void setStubKeySetId(byte[] keySetId)
      throws android.media.NotProvisionedException, android.media.DeniedByServerException {
    when(mediaDrm.provideKeyResponse(any(byte[].class), any())).thenReturn(keySetId);
  }

  private static void assertOfflineLicenseKeySetIdEqual(
      byte[] expectedKeySetId, byte[] actualKeySetId) throws Exception {
    assertThat(actualKeySetId).isNotNull();
    assertThat(actualKeySetId).isEqualTo(expectedKeySetId);
  }

  private void setStubLicenseAndPlaybackDurationValues(
      long licenseDuration, long playbackDuration) {
    HashMap<String, String> keyStatus = new HashMap<>();
    keyStatus.put(
        WidevineUtil.PROPERTY_LICENSE_DURATION_REMAINING, String.valueOf(licenseDuration));
    keyStatus.put(
        WidevineUtil.PROPERTY_PLAYBACK_DURATION_REMAINING, String.valueOf(playbackDuration));
    when(mediaDrm.queryKeyStatus(any(byte[].class))).thenReturn(keyStatus);
  }

  private static DrmInitData newDrmInitData() {
    return new DrmInitData(
        new SchemeData(C.WIDEVINE_UUID, "mimeType", new byte[] {1, 4, 7, 0, 3, 6}));
  }
}