DatabasePersistence extends Persistence { @Override @Nullable public String getLogs(@NonNull String group, @NonNull Collection<String> pausedTargetKeys, @IntRange(from = 0) int limit, @NonNull List<Log> outLogs) { AppCenterLog.debug(LOG_TAG, "Trying to get " + limit + " logs from the Persistence database for " + group); SQLiteQueryBuilder builder = SQLiteUtils.newSQLiteQueryBuilder(); builder.appendWhere(COLUMN_GROUP + " = ?"); List<String> selectionArgs = new ArrayList<>(); selectionArgs.add(group); if (!pausedTargetKeys.isEmpty()) { StringBuilder filter = new StringBuilder(); for (int i = 0; i < pausedTargetKeys.size(); i++) { filter.append("?,"); } filter.deleteCharAt(filter.length() - 1); builder.appendWhere(" AND "); builder.appendWhere(COLUMN_TARGET_KEY + " NOT IN (" + filter.toString() + ")"); selectionArgs.addAll(pausedTargetKeys); } int count = 0; Map<Long, Log> candidates = new LinkedHashMap<>(); List<Long> failedDbIdentifiers = new ArrayList<>(); File largePayloadGroupDirectory = getLargePayloadGroupDirectory(group); String[] selectionArgsArray = selectionArgs.toArray(new String[0]); Cursor cursor = null; ContentValues values; try { cursor = mDatabaseManager.getCursor(builder, null, selectionArgsArray, GET_SORT_ORDER); } catch (RuntimeException e) { AppCenterLog.error(LOG_TAG, "Failed to get logs: ", e); } while (cursor != null && (values = mDatabaseManager.nextValues(cursor)) != null && count < limit) { Long dbIdentifier = values.getAsLong(PRIMARY_KEY); if (dbIdentifier == null) { AppCenterLog.error(LOG_TAG, "Empty database record, probably content was larger than 2MB, need to delete as it's now corrupted."); List<Long> corruptedIds = getLogsIds(builder, selectionArgsArray); for (Long corruptedId : corruptedIds) { if (!mPendingDbIdentifiers.contains(corruptedId) && !candidates.containsKey(corruptedId)) { deleteLog(largePayloadGroupDirectory, corruptedId); AppCenterLog.error(LOG_TAG, "Empty database corrupted empty record deleted, id=" + corruptedId); break; } } continue; } if (!mPendingDbIdentifiers.contains(dbIdentifier)) { try { String logPayload; String databasePayload = values.getAsString(COLUMN_LOG); if (databasePayload == null) { File file = getLargePayloadFile(largePayloadGroupDirectory, dbIdentifier); AppCenterLog.debug(LOG_TAG, "Read payload file " + file); logPayload = FileManager.read(file); if (logPayload == null) { throw new JSONException("Log payload is null and not stored as a file."); } } else { logPayload = databasePayload; } String databasePayloadType = values.getAsString(COLUMN_DATA_TYPE); Log log = getLogSerializer().deserializeLog(logPayload, databasePayloadType); String targetToken = values.getAsString(COLUMN_TARGET_TOKEN); if (targetToken != null) { CryptoUtils.DecryptedData data = CryptoUtils.getInstance(mContext).decrypt(targetToken); log.addTransmissionTarget(data.getDecryptedData()); } candidates.put(dbIdentifier, log); count++; } catch (JSONException e) { AppCenterLog.error(LOG_TAG, "Cannot deserialize a log in the database", e); failedDbIdentifiers.add(dbIdentifier); } } } if (cursor != null) { try { cursor.close(); } catch (RuntimeException ignore) { } } if (failedDbIdentifiers.size() > 0) { for (long id : failedDbIdentifiers) { deleteLog(largePayloadGroupDirectory, id); } AppCenterLog.warn(LOG_TAG, "Deleted logs that cannot be deserialized"); } if (candidates.size() <= 0) { AppCenterLog.debug(LOG_TAG, "No logs found in the Persistence database at the moment"); return null; } String id = UUID.randomUUID().toString(); AppCenterLog.debug(LOG_TAG, "Returning " + candidates.size() + " log(s) with an ID, " + id); AppCenterLog.debug(LOG_TAG, "The SID/ID pairs for returning log(s) is/are:"); List<Long> pendingDbIdentifiersGroup = new ArrayList<>(); for (Map.Entry<Long, Log> entry : candidates.entrySet()) { Long dbIdentifier = entry.getKey(); mPendingDbIdentifiers.add(dbIdentifier); pendingDbIdentifiersGroup.add(dbIdentifier); outLogs.add(entry.getValue()); AppCenterLog.debug(LOG_TAG, "\t" + entry.getValue().getSid() + " / " + dbIdentifier); } mPendingDbIdentifiersGroups.put(group + id, pendingDbIdentifiersGroup); return id; } DatabasePersistence(Context context);   DatabasePersistence(Context context, int version, @SuppressWarnings("SameParameterValue") final ContentValues schema); @Override boolean setMaxStorageSize(long maxStorageSizeInBytes); @Override long putLog(@NonNull Log log, @NonNull String group, @IntRange(from = Flags.NORMAL, to = Flags.CRITICAL) int flags); @Override void deleteLogs(@NonNull String group, @NonNull String id); @Override void deleteLogs(String group); @Override int countLogs(@NonNull String group); @Override @Nullable String getLogs(@NonNull String group, @NonNull Collection<String> pausedTargetKeys, @IntRange(from = 0) int limit, @NonNull List<Log> outLogs); @Override void clearPendingLogState(); @Override void close();  }
@Test public void getLogsWithGetCursorException() throws Exception { mockStatic(AppCenterLog.class); DatabaseManager databaseManager = mock(DatabaseManager.class); whenNew(DatabaseManager.class).withAnyArguments().thenReturn(databaseManager); when(databaseManager.nextValues(any(Cursor.class))).thenCallRealMethod(); when(databaseManager.getCursor(any(SQLiteQueryBuilder.class), any(String[].class), any(String[].class), anyString())).thenThrow(new RuntimeException()); DatabasePersistence persistence = new DatabasePersistence(mock(Context.class), 1, DatabasePersistence.SCHEMA); ArrayList<Log> outLogs = new ArrayList<>(); persistence.getLogs("mock", Collections.<String>emptyList(), 50, outLogs); assertEquals(0, outLogs.size()); verifyStatic(); AppCenterLog.error(eq(AppCenter.LOG_TAG), anyString(), any(RuntimeException.class)); }
@Test public void getLogsWithMoveNextException() throws Exception { mockStatic(AppCenterLog.class); DatabaseManager databaseManager = mock(DatabaseManager.class); whenNew(DatabaseManager.class).withAnyArguments().thenReturn(databaseManager); when(databaseManager.nextValues(any(Cursor.class))).thenCallRealMethod(); Cursor mockCursor = mock(Cursor.class); when(mockCursor.moveToNext()).thenThrow(new RuntimeException()); when(databaseManager.getCursor(any(SQLiteQueryBuilder.class), any(String[].class), any(String[].class), anyString())).thenReturn(mockCursor); DatabasePersistence persistence = new DatabasePersistence(mock(Context.class), 1, DatabasePersistence.SCHEMA); ArrayList<Log> outLogs = new ArrayList<>(); persistence.getLogs("mock", Collections.<String>emptyList(), 50, outLogs); assertEquals(0, outLogs.size()); verifyStatic(); AppCenterLog.error(eq(AppCenter.LOG_TAG), anyString(), any(RuntimeException.class)); }
@Test public void getLogsWithGetCorruptedIdsException() throws Exception { mockStatic(AppCenterLog.class); DatabaseManager databaseManager = mock(DatabaseManager.class); whenNew(DatabaseManager.class).withAnyArguments().thenReturn(databaseManager); when(databaseManager.nextValues(any(Cursor.class))).thenCallRealMethod(); List<ContentValues> fieldValues = new ArrayList<>(); { ContentValues contentValues = mock(ContentValues.class); when(contentValues.getAsLong(DatabaseManager.PRIMARY_KEY)).thenReturn(null); fieldValues.add(contentValues); } MockCursor mockCursor = new MockCursor(fieldValues); mockCursor.mockBuildValues(databaseManager); when(databaseManager.getCursor(any(SQLiteQueryBuilder.class), isNull(String[].class), any(String[].class), anyString())).thenReturn(mockCursor); Cursor failingCursor = mock(Cursor.class); when(failingCursor.moveToNext()).thenThrow(new SQLiteDiskIOException()); when(databaseManager.getCursor(any(SQLiteQueryBuilder.class), isNotNull(String[].class), any(String[].class), anyString())).thenReturn(failingCursor); DatabasePersistence persistence = new DatabasePersistence(mock(Context.class)); ArrayList<Log> outLogs = new ArrayList<>(); persistence.getLogs("mock", Collections.<String>emptyList(), 50, outLogs); assertEquals(0, outLogs.size()); verifyStatic(); AppCenterLog.error(eq(AppCenter.LOG_TAG), anyString(), any(RuntimeException.class)); }
@Test public void getLogsWithCorruption() throws Exception { int logCount = 3; DatabaseManager databaseManager = mock(DatabaseManager.class); whenNew(DatabaseManager.class).withAnyArguments().thenReturn(databaseManager); when(databaseManager.nextValues(any(Cursor.class))).thenCallRealMethod(); List<ContentValues> fieldValues = new ArrayList<>(logCount); { ContentValues contentValues = mock(ContentValues.class); when(contentValues.getAsLong(DatabaseManager.PRIMARY_KEY)).thenReturn(0L); when(contentValues.getAsString(DatabasePersistence.COLUMN_LOG)).thenReturn("first"); fieldValues.add(contentValues); } { ContentValues contentValues = mock(ContentValues.class); when(contentValues.getAsLong(DatabaseManager.PRIMARY_KEY)).thenReturn(null); fieldValues.add(contentValues); } { ContentValues contentValues = mock(ContentValues.class); when(contentValues.getAsLong(DatabaseManager.PRIMARY_KEY)).thenReturn(2L); when(contentValues.getAsString(DatabasePersistence.COLUMN_LOG)).thenReturn("last"); fieldValues.add(contentValues); } MockCursor mockCursor = new MockCursor(fieldValues); mockCursor.mockBuildValues(databaseManager); when(databaseManager.getCursor(any(SQLiteQueryBuilder.class), isNull(String[].class), any(String[].class), anyString())).thenReturn(mockCursor); List<ContentValues> idValues = new ArrayList<>(logCount); for (long i = 0; i < logCount; i++) { ContentValues contentValues = mock(ContentValues.class); when(contentValues.getAsLong(DatabaseManager.PRIMARY_KEY)).thenReturn(i); idValues.add(contentValues); } MockCursor mockIdCursor = new MockCursor(idValues); mockIdCursor.mockBuildValues(databaseManager); when(databaseManager.getCursor(any(SQLiteQueryBuilder.class), isNotNull(String[].class), any(String[].class), anyString())).thenReturn(mockIdCursor); LogSerializer logSerializer = mock(LogSerializer.class); when(logSerializer.deserializeLog(anyString(), anyString())).thenAnswer(new Answer<Log>() { @Override public Log answer(InvocationOnMock invocation) { Log log = mock(Log.class); when(log.getType()).thenReturn((String) invocation.getArguments()[0]); return log; } }); DatabasePersistence persistence = new DatabasePersistence(mock(Context.class)); persistence.setLogSerializer(logSerializer); ArrayList<Log> outLogs = new ArrayList<>(); persistence.getLogs("mock", Collections.<String>emptyList(), 50, outLogs); assertEquals(logCount - 1, outLogs.size()); assertEquals("first", outLogs.get(0).getType()); assertEquals("last", outLogs.get(1).getType()); verify(databaseManager).delete(1); outLogs = new ArrayList<>(); persistence.getLogs("mock", Collections.<String>emptyList(), 50, outLogs); assertEquals(0, outLogs.size()); logCount = 4; fieldValues = new ArrayList<>(logCount); { ContentValues contentValues = mock(ContentValues.class); when(contentValues.getAsLong(DatabaseManager.PRIMARY_KEY)).thenReturn(0L); when(contentValues.getAsString(DatabasePersistence.COLUMN_LOG)).thenReturn("first"); fieldValues.add(contentValues); } { ContentValues contentValues = mock(ContentValues.class); when(contentValues.getAsLong(DatabaseManager.PRIMARY_KEY)).thenReturn(2L); when(contentValues.getAsString(DatabasePersistence.COLUMN_LOG)).thenReturn("last"); fieldValues.add(contentValues); } { ContentValues contentValues = mock(ContentValues.class); when(contentValues.getAsLong(DatabaseManager.PRIMARY_KEY)).thenReturn(null); fieldValues.add(contentValues); } { ContentValues contentValues = mock(ContentValues.class); when(contentValues.getAsLong(DatabaseManager.PRIMARY_KEY)).thenReturn(4L); when(contentValues.getAsString(DatabasePersistence.COLUMN_LOG)).thenReturn("true last"); fieldValues.add(contentValues); } mockCursor = new MockCursor(fieldValues) { @Override public void close() { throw new RuntimeException(); } }; mockCursor.mockBuildValues(databaseManager); when(databaseManager.getCursor(any(SQLiteQueryBuilder.class), isNull(String[].class), any(String[].class), anyString())).thenReturn(mockCursor); idValues = new ArrayList<>(4); for (long i = 0; i < logCount; i += 2) { ContentValues contentValues = mock(ContentValues.class); when(contentValues.getAsLong(DatabaseManager.PRIMARY_KEY)).thenReturn(i); idValues.add(contentValues); } mockIdCursor = new MockCursor(idValues) { @Override public void close() { throw new RuntimeException(); } }; mockIdCursor.mockBuildValues(databaseManager); when(databaseManager.getCursor(any(SQLiteQueryBuilder.class), isNotNull(String[].class), any(String[].class), anyString())).thenReturn(mockIdCursor); outLogs = new ArrayList<>(); persistence.getLogs("mock", Collections.<String>emptyList(), 50, outLogs); assertEquals(1, outLogs.size()); assertEquals("true last", outLogs.get(0).getType()); verify(databaseManager).delete(anyLong()); }