/* * Copyright (C) 2025 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.android.privatespace import android.content.ContentResolver import android.net.Uri import android.os.Environment import android.os.ParcelFileDescriptor import android.system.Os import android.system.StructStatVfs import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.google.common.truth.Truth.assertThat import java.io.File import java.io.FileNotFoundException import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.MockitoSession import org.mockito.kotlin.any import org.mockito.kotlin.doNothing import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import org.mockito.quality.Strictness @RunWith(AndroidJUnit4::class) class StorageHelperImplTest { private lateinit var mockContentResolver: ContentResolver private lateinit var storageHelper: StorageHelper private lateinit var mockitoSession: MockitoSession private val uris = listOf(TEST_URI_1, TEST_URI_2) private val pfd1 = mockParcelFileDescriptor(PDF_1_FILE_SIZE) private val pfd2 = mockParcelFileDescriptor(PDF_2_FILE_SIZE) companion object { private val TEST_URI_1 = Uri.parse("content://dummy/1") private val TEST_URI_2 = Uri.parse("content://dummy/2") private const val PDF_1_FILE_SIZE: Long = 1024L private const val PDF_2_FILE_SIZE: Long = 2048L private const val TEST_BLOCK_SIZE = 4096L private const val TEST_AVAILABLE_BLOCKS = 1_000_000L private const val AVAILABLE_DEVICE_STORAGE_BUFFER = 256_000_000L } @Before fun setUp() { mockitoSession = mockitoSession() .strictness(Strictness.LENIENT) .mockStatic(Environment::class.java) .mockStatic(Os::class.java) .startMocking() mockContentResolver = mock() storageHelper = StorageHelperImpl } @After fun tearDown() { mockitoSession.finishMocking() } private fun mockParcelFileDescriptor(size: Long): ParcelFileDescriptor { val mockPfd: ParcelFileDescriptor = mock() whenever(mockPfd.statSize).thenReturn(size) doNothing().whenever(mockPfd).close() return mockPfd } @Test fun getAvailableDeviceStorage_returnsCorrectCalculation() = runTest { val mockFile = mock() whenever(mockFile.path).thenReturn("/storage/emulated/0") ExtendedMockito.doReturn(mockFile).`when` { Environment.getExternalStorageDirectory() } val realStructStatVfs = StructStatVfs( /* f_bsize */ 0L, /* f_frsize */ TEST_BLOCK_SIZE, /* f_blocks */ 0L, /* f_bfree */ 0L, /* f_bavail */ TEST_AVAILABLE_BLOCKS, /* f_files */ 0L, /* f_ffree */ 0L, /* f_favail */ 0L, /* f_fsid */ 0L, /* f_flag */ 0L, /* f_namemax */ 0L, ) ExtendedMockito.doReturn(realStructStatVfs).`when` { Os.statvfs(any()) } val expectedAvailableBytes = (TEST_AVAILABLE_BLOCKS * TEST_BLOCK_SIZE) - AVAILABLE_DEVICE_STORAGE_BUFFER val actualAvailableBytes = storageHelper.getAvailableDeviceStorage() assertThat(actualAvailableBytes).isEqualTo(expectedAvailableBytes) } @Test fun calculateTotalSize_success_returnsTotalSize() = runTest { whenever(mockContentResolver.openFileDescriptor(TEST_URI_1, "r")).thenReturn(pfd1) whenever(mockContentResolver.openFileDescriptor(TEST_URI_2, "r")).thenReturn(pfd2) val totalSize = storageHelper.calculateTotalSize(mockContentResolver, uris) { _, _ -> } assertThat(totalSize).isEqualTo(PDF_1_FILE_SIZE + PDF_2_FILE_SIZE) } @Test fun calculateTotalSize_fileNotFound_callsOnError() = runTest { var errorUri: Uri? = null var errorException: Exception? = null whenever(mockContentResolver.openFileDescriptor(TEST_URI_1, "r")).thenReturn(pfd1) whenever(mockContentResolver.openFileDescriptor(TEST_URI_2, "r")) .thenThrow(FileNotFoundException("File not found")) val totalSize = storageHelper.calculateTotalSize(mockContentResolver, uris) { uri, e -> errorUri = uri errorException = e } assertThat(totalSize).isEqualTo(PDF_1_FILE_SIZE) assertThat(errorUri).isEqualTo(TEST_URI_2) assertThat(errorException).isInstanceOf(FileNotFoundException::class.java) } @Test fun calculateTotalSize_nullFileDescriptor_callsOnError() = runTest { var errorUri: Uri? = null var errorException: Exception? = null whenever(mockContentResolver.openFileDescriptor(TEST_URI_1, "r")).thenReturn(pfd1) whenever(mockContentResolver.openFileDescriptor(TEST_URI_2, "r")).thenReturn(null) val totalSize = storageHelper.calculateTotalSize(mockContentResolver, uris) { uri, e -> errorUri = uri errorException = e } assertThat(totalSize).isEqualTo(PDF_1_FILE_SIZE) assertThat(errorUri).isEqualTo(TEST_URI_2) assertThat(errorException).isInstanceOf(IllegalStateException::class.java) } }