Отличное форматирование
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
|
||||
#v(0.5em)
|
||||
|
||||
This explanatory note describes the development of the Wallenc mobile application — a client-side wallet for secure storage of user data on untrusted storage backends without a dedicated application server. Security is ensured by client-side encryption before data leaves the device; decryption is performed only inside the application when the user supplies a valid key. The work covers analysis of the problem domain and competing products, requirements specification, system and user-interface design, Kotlin implementation for Android, software testing, and a brief economic assessment.
|
||||
The thesis describes the Wallenc mobile application — a client wallet for storing data on untrusted backends without a dedicated application server. Data is encrypted on the device before upload; decryption is performed only when the user enters a valid key. The work includes analysis of analogues, requirements, architecture and UI design, Kotlin implementation for Android, testing, and a brief economic assessment.
|
||||
|
||||
The project applies MVVM and Clean Architecture with Gradle modules (`:app`, `:domain`, `:usecases`, `:ui`, `:domain-vault`, `:infrastructure-android`, `:vault-contracts`, `:task-runtime`). Functional capabilities include local and remote vault management, AES-based encryption, Room metadata storage, Yandex OAuth integration, and a planned synchronization model that does not expose encryption keys to external providers.
|
||||
The application uses MVVM and Clean Architecture. Main features are local and remote vault management, client-side AES encryption, Room metadata storage, and Yandex OAuth integration.
|
||||
|
||||
Keywords: mobile application, client-side encryption, Android, vault, zero-knowledge, OAuth, Room, Jetpack Compose.
|
||||
Keywords: mobile application, client-side encryption, Android, vault, OAuth, Room.
|
||||
|
||||
@@ -87,11 +87,11 @@ viewModel.yandexSignIn.launch { outcome ->
|
||||
|
||||
== Взаимодействие подсистем и итоговая архитектура
|
||||
|
||||
Зависимости модулей Gradle показаны на рисунке @fig-23. Полный исходный код всех модулей сборки приведён в *приложении А* (307 файлов, сгенерировано скриптом `gen_listings.py`).
|
||||
Зависимости модулей Gradle показаны на рисунке @fig-23. Полный исходный код модулей сборки приведён в приложении А.
|
||||
|
||||
#pz-fig("fig_23_module_deps.png", [Зависимости модулей Gradle], "fig-23")
|
||||
|
||||
В основном тексте приведены показательные фрагменты; листинги по модулям `:domain`, `:infrastructure-android`, `:app` — в приложении А, разделы «Модуль :domain» и др.
|
||||
В основном тексте приведены показательные фрагменты; полные листинги — в приложении А.
|
||||
|
||||
#include "ch04-expand.typ"
|
||||
#include "ch04-modules.typ"
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
#import "common.typ": pz-table
|
||||
|
||||
=== Детальное описание тестов EncryptorTest
|
||||
|
||||
Класс `EncryptorTest` — эталонный набор для приёмки криптографического ядра. Каждый метод изолирован и не зависит от порядка выполнения.
|
||||
|
||||
#pz-table(
|
||||
[Методы EncryptorTest],
|
||||
3,
|
||||
table.header([Метод], [Проверяемое поведение], [OK]),
|
||||
[test correct key for StorageEncryptionInfo], [`checkKey` → true], [+],
|
||||
[test incorrect key for StorageEncryptionInfo], [`checkKey` → false], [+],
|
||||
[test string encryption with the same key], [Симметрия encrypt/decrypt], [+],
|
||||
[test string encryption with the wrong key], [Исключение при decrypt], [+],
|
||||
[test bytes encryption with the same key], [Восстановление 512 байт], [+],
|
||||
[test bytes encryption with the wrong key], [Исключение], [+],
|
||||
[test stream encryption with the same key], [Поток 1500 байт], [+],
|
||||
[test stream encryption with the wrong key], [Исключение decryptStream], [+],
|
||||
) <tbl-encryptor-detail>
|
||||
|
||||
Методика: для потоков используется `ByteArrayOutputStream` с запасом ёмкости `dataLen*3`, чтобы учесть расширение ciphertext.
|
||||
|
||||
=== Детальное описание тестов StorageSyncEngineTest
|
||||
|
||||
Движок синхронизации тестируется на in-memory двойниках хранилищ. Полный реестр методов — в таблице модуля `:usecases` выше.
|
||||
@@ -1,24 +0,0 @@
|
||||
#import "common.typ": pz-table
|
||||
|
||||
=== Каталог тестов StorageSyncEngineTest
|
||||
|
||||
#pz-table(
|
||||
[StorageSyncEngineTest — 12 методов],
|
||||
3,
|
||||
table.header([Метод], [Поведение], [+]),
|
||||
[syncAllGroupsReportsNoGroupsWhenEmpty], [Нет групп → отчёт], [+],
|
||||
[syncGroupCopiesFileFromSourceToTarget], [Копия на target], [+],
|
||||
[syncGroupSkippedWhenFewerThanTwoStorages], [Skip при менее 2 storage], [+],
|
||||
[syncGroupDeleteRemovesFileOnTarget], [Удаление на target], [+],
|
||||
[syncSkipsWhenTargetRevisionAlreadyWinner], [Ревизия-победитель], [+],
|
||||
[openReadDoesNotChangeJournal], [Чтение без журнала], [+],
|
||||
[deleteWithRecordSyncJournalFalseDoesNotBumpSequence], [Delete без журнала], [+],
|
||||
[syncGroupTrashSoftDeletesOnTarget], [Trash на target], [+],
|
||||
[syncGroupStopsWhenLockCannotBeAcquired], [Стоп при lock], [+],
|
||||
[syncGroupReleasesLocksAfterSuccessfulSync], [Unlock после успеха], [+],
|
||||
[syncGroupReleasesLocksWhenJournalReadFails], [Unlock при ошибке], [+],
|
||||
[syncGroupCooperativeCancellationReleasesLocks], [Unlock при отмене], [+],
|
||||
[syncGroupReleasesLocksWhenJournalEmpty], [Пустой журнал], [+],
|
||||
) <tbl-sync-engine>
|
||||
|
||||
Тесты `StorageSyncJournalMergeTest` и `StorageSyncEncryptionCompatTest` дополняют движок проверкой слияния журнала и совместимости шифрования в группе.
|
||||
@@ -2,209 +2,81 @@
|
||||
#import "common.typ": pz-test-table
|
||||
|
||||
#pz-test-table(
|
||||
[Сводка модульных unit-тестов (src/test)],
|
||||
[Реестр модульных unit-тестов],
|
||||
4,
|
||||
table.header(
|
||||
[№],
|
||||
[Модуль],
|
||||
[Класс],
|
||||
[Метод],
|
||||
[Файл],
|
||||
[Проверяемое поведение],
|
||||
),
|
||||
[domain], [EncryptorTest], [test correct key for StorageEncryptionInfo], [EncryptorTest.kt],
|
||||
[domain], [EncryptorTest], [test incorrect key for StorageEncryptionInfo], [EncryptorTest.kt],
|
||||
[domain], [EncryptorTest], [test string encryption with the same key], [EncryptorTest.kt],
|
||||
[domain], [EncryptorTest], [test string encryption with the wrong key], [EncryptorTest.kt],
|
||||
[domain], [EncryptorTest], [test bytes encryption with the same key], [EncryptorTest.kt],
|
||||
[domain], [EncryptorTest], [test bytes encryption with the wrong key], [EncryptorTest.kt],
|
||||
[domain], [EncryptorTest], [test stream encryption with the same key], [EncryptorTest.kt],
|
||||
[domain], [EncryptorTest], [test stream encryption with the wrong key], [EncryptorTest.kt],
|
||||
[domain], [WallencExceptionMappingTest], [preservesWallencException], [WallencExceptionMappingTest.kt],
|
||||
[domain], [WallencExceptionMappingTest], [mapsFileNotFoundException], [WallencExceptionMappingTest.kt],
|
||||
[domain], [WallencExceptionMappingTest], [mapsIOExceptionToIoFailed], [WallencExceptionMappingTest.kt],
|
||||
[domain], [WallencExceptionMappingTest], [mapsGenericExceptionToUnknown], [WallencExceptionMappingTest.kt],
|
||||
[domain-vault], [VaultThrowableMappingTest], [mapsYandexDiskAuthToAuthFailed], [VaultThrowableMappingTest.kt],
|
||||
[domain-vault], [VaultThrowableMappingTest], [mapsHttpExceptionToNetworkHttpFailed], [VaultThrowableMappingTest.kt],
|
||||
[domain-vault], [VaultThrowableMappingTest], [mapsMissingOAuthTokenIoToTokenMissing], [VaultThrowableMappingTest.kt],
|
||||
[domain-vault], [VaultThrowableMappingTest], [mapsSocketTimeoutToOperationTimedOut], [VaultThrowableMappingTest.kt],
|
||||
[domain-vault], [VaultThrowableMappingTest], [mapsFileNotFoundToStorageFileNotFound], [VaultThrowableMappingTest.kt],
|
||||
[domain-vault], [VaultThrowableMappingTest], [mapsIllegalStateNotAFile], [VaultThrowableMappingTest.kt],
|
||||
[domain-vault], [YandexDiskRepositoryTest], [diskInfoParsesResponse], [YandexDiskRepositoryTest.kt],
|
||||
[domain-vault], [YandexDiskRepositoryTest], [listReturnsEmptyEmbeddedOn404], [YandexDiskRepositoryTest.kt],
|
||||
[domain-vault], [YandexDiskRepositoryTest], [diskInfoThrowsAuthExceptionOn401], [YandexDiskRepositoryTest.kt],
|
||||
[domain-vault], [StorageSyncJournalBufferTest], [flushRestoresPendingOnWriteFailure], [StorageSyncJournalBufferTest.kt],
|
||||
[task-runtime], [TaskOrchestratorTest], [enqueueCompletesTask], [TaskOrchestratorTest.kt],
|
||||
[task-runtime], [TaskOrchestratorTest], [cancelAllMarksRunningTaskCancelled], [TaskOrchestratorTest.kt],
|
||||
[task-runtime], [TaskOrchestratorTest], [cancelMarksTaskCancelled], [TaskOrchestratorTest.kt],
|
||||
[task-runtime], [TaskOrchestratorTest], [failRecordsFailedState], [TaskOrchestratorTest.kt],
|
||||
[task-runtime], [TaskOrchestratorTest], [progressUpdatesRunningState], [TaskOrchestratorTest.kt],
|
||||
[task-runtime], [TaskOrchestratorTest], [logAppendsLine], [TaskOrchestratorTest.kt],
|
||||
[ui], [WallencDeepLinksTest], [matchesWallencViewIntent], [WallencDeepLinksTest.kt],
|
||||
[ui], [WallencDeepLinksTest], [rejectsUnrelatedIntent], [WallencDeepLinksTest.kt],
|
||||
[ui], [WallencDeepLinksTest], [matchesTasksAndSettingsHosts], [WallencDeepLinksTest.kt],
|
||||
[ui], [TaskProgressLabelsTest], [syncNoGroups_mapsToStringRes], [TaskProgressLabelsTest.kt],
|
||||
[ui], [TaskProgressLabelsTest], [vaultTask_mapsToStringRes], [TaskProgressLabelsTest.kt],
|
||||
[ui], [TaskProgressLabelsTest], [clearContentProgress_mapsToStringRes], [TaskProgressLabelsTest.kt],
|
||||
[ui], [WallencUserNotificationMappingTest], [mapsFeatureStorageNotFound], [WallencUserNotificationMappingTest.kt],
|
||||
[ui], [WallencUserNotificationMappingTest], [mapsStorageIncorrectKey], [WallencUserNotificationMappingTest.kt],
|
||||
[ui], [WallencUserNotificationMappingTest], [mapsUnknown], [WallencUserNotificationMappingTest.kt],
|
||||
[ui], [StorageNavigationRoutesSmokeTest], [storageHomeRouteCarriesVaultAndStorageIds], [StorageNavigationRoutesSmokeTest.kt],
|
||||
[ui], [StorageNavigationRoutesSmokeTest], [textSecretsRoutesCarryRequiredArguments], [StorageNavigationRoutesSmokeTest.kt],
|
||||
[ui], [OtpAuthUriParserTest], [parsesStandardTotpUri], [OtpAuthUriParserTest.kt],
|
||||
[ui], [OtpAuthUriParserTest], [rejectsNonOtpauthScheme], [OtpAuthUriParserTest.kt],
|
||||
[ui], [OtpAuthUriParserTest], [rejectsMissingSecret], [OtpAuthUriParserTest.kt],
|
||||
[ui], [TaskPipelineViewModelTest], [startTestTaskEnqueuesWork], [TaskPipelineViewModelTest.kt],
|
||||
[usecases], [StorageDomainUseCasesTest], [twoFaCrudWorksAndPersists], [StorageDomainUseCasesTest.kt],
|
||||
[usecases], [StorageDomainUseCasesTest], [twoFaInvalidJsonFallsBackToEmptyList], [StorageDomainUseCasesTest.kt],
|
||||
[usecases], [StorageDomainUseCasesTest], [textSecretsCrudWorksWithOptionalLabels], [StorageDomainUseCasesTest.kt],
|
||||
[usecases], [StorageDomainUseCasesTest], [textSecretsInvalidJsonFallsBackToEmptyList], [StorageDomainUseCasesTest.kt],
|
||||
[usecases], [StorageSyncEncryptionCompatTest], [storageWithoutEncInfoIsCompatible], [StorageSyncEncryptionCompatTest.kt],
|
||||
[usecases], [StorageSyncEncryptionCompatTest], [storageWithEncInfoIsIncompatible], [StorageSyncEncryptionCompatTest.kt],
|
||||
[usecases], [StorageSyncEngineTest], [syncAllGroupsReportsNoGroupsWhenEmpty], [StorageSyncEngineTest.kt],
|
||||
[usecases], [StorageSyncEngineTest], [syncGroupCopiesFileFromSourceToTarget], [StorageSyncEngineTest.kt],
|
||||
[usecases], [StorageSyncEngineTest], [syncGroupSkippedWhenFewerThanTwoStorages], [StorageSyncEngineTest.kt],
|
||||
[usecases], [StorageSyncEngineTest], [syncGroupDeleteRemovesFileOnTarget], [StorageSyncEngineTest.kt],
|
||||
[usecases], [StorageSyncEngineTest], [syncSkipsWhenTargetRevisionAlreadyWinner], [StorageSyncEngineTest.kt],
|
||||
[usecases], [StorageSyncEngineTest], [openReadDoesNotChangeJournal], [StorageSyncEngineTest.kt],
|
||||
[usecases], [StorageSyncEngineTest], [deleteWithRecordSyncJournalFalseDoesNotBumpSequence], [StorageSyncEngineTest.kt],
|
||||
[usecases], [StorageSyncEngineTest], [syncGroupTrashSoftDeletesOnTarget], [StorageSyncEngineTest.kt],
|
||||
[usecases], [StorageSyncEngineTest], [syncGroupStopsWhenLockCannotBeAcquired], [StorageSyncEngineTest.kt],
|
||||
[usecases], [StorageSyncEngineTest], [syncGroupReleasesLocksAfterSuccessfulSync], [StorageSyncEngineTest.kt],
|
||||
[usecases], [StorageSyncEngineTest], [syncGroupReleasesLocksWhenJournalReadFails], [StorageSyncEngineTest.kt],
|
||||
[usecases], [StorageSyncEngineTest], [syncGroupCooperativeCancellationReleasesLocks], [StorageSyncEngineTest.kt],
|
||||
[usecases], [StorageSyncEngineTest], [syncGroupReleasesLocksWhenJournalEmpty], [StorageSyncEngineTest.kt],
|
||||
[usecases], [StorageSyncJournalMergeTest], [mergeKeepsSingleEntryPerPath], [StorageSyncJournalMergeTest.kt],
|
||||
[usecases], [StorageSyncJournalMergeTest], [isSyncableUserPathExcludesEncDirAndJournal], [StorageSyncJournalMergeTest.kt],
|
||||
[usecases], [TwoFaTotpTest], [buildTwoFaCodeStateMatchesJavaOtpForKnownSecret], [TwoFaTotpTest.kt],
|
||||
[usecases], [TwoFaTotpTest], [totpPeriodProgressIsContinuousWithinPeriod], [TwoFaTotpTest.kt],
|
||||
[usecases], [TwoFaTotpTest], [totpSecondsUntilRefreshCountsDownWithinPeriod], [TwoFaTotpTest.kt],
|
||||
[usecases], [TwoFaTotpTest], [buildTwoFaCodeStateReturnsNullForInvalidSecret], [TwoFaTotpTest.kt],
|
||||
[1], [domain], [mapsFileNotFoundException], [исключение преобразуется в типизированную ошибку Wallenc],
|
||||
[2], [domain], [mapsGenericExceptionToUnknown], [исключение преобразуется в типизированную ошибку Wallenc],
|
||||
[3], [domain], [mapsIOExceptionToIoFailed], [исключение преобразуется в типизированную ошибку Wallenc],
|
||||
[4], [domain], [preservesWallencException], [сохранение уже типизированного WallencException],
|
||||
[5], [domain], [test bytes encryption with the same key], [симметрия шифрования и дешифрования при верном ключе],
|
||||
[6], [domain], [test bytes encryption with the wrong key], [дешифрование с неверным ключом завершается ошибкой],
|
||||
[7], [domain], [test correct key for StorageEncryptionInfo], [верный ключ проходит проверку checkKey],
|
||||
[8], [domain], [test incorrect key for StorageEncryptionInfo], [верный ключ проходит проверку checkKey],
|
||||
[9], [domain], [test stream encryption with the same key], [симметрия шифрования и дешифрования при верном ключе],
|
||||
[10], [domain], [test stream encryption with the wrong key], [дешифрование с неверным ключом завершается ошибкой],
|
||||
[11], [domain], [test string encryption with the same key], [симметрия шифрования и дешифрования при верном ключе],
|
||||
[12], [domain], [test string encryption with the wrong key], [дешифрование с неверным ключом завершается ошибкой],
|
||||
[13], [domain-vault], [diskInfoParsesResponse], [разбор ответа API diskInfo],
|
||||
[14], [domain-vault], [diskInfoThrowsAuthExceptionOn401], [AuthException при HTTP 401],
|
||||
[15], [domain-vault], [flushRestoresPendingOnWriteFailure], [откат буфера журнала при сбое записи],
|
||||
[16], [domain-vault], [listReturnsEmptyEmbeddedOn404], [пустой список при HTTP 404],
|
||||
[17], [domain-vault], [mapsFileNotFoundToStorageFileNotFound], [исключение преобразуется в типизированную ошибку Wallenc],
|
||||
[18], [domain-vault], [mapsHttpExceptionToNetworkHttpFailed], [исключение преобразуется в типизированную ошибку Wallenc],
|
||||
[19], [domain-vault], [mapsIllegalStateNotAFile], [исключение преобразуется в типизированную ошибку Wallenc],
|
||||
[20], [domain-vault], [mapsMissingOAuthTokenIoToTokenMissing], [исключение преобразуется в типизированную ошибку Wallenc],
|
||||
[21], [domain-vault], [mapsSocketTimeoutToOperationTimedOut], [исключение преобразуется в типизированную ошибку Wallenc],
|
||||
[22], [domain-vault], [mapsYandexDiskAuthToAuthFailed], [исключение преобразуется в типизированную ошибку Wallenc],
|
||||
[23], [task-runtime], [cancelAllMarksRunningTaskCancelled], [жизненный цикл фоновой задачи],
|
||||
[24], [task-runtime], [cancelMarksTaskCancelled], [жизненный цикл фоновой задачи],
|
||||
[25], [task-runtime], [enqueueCompletesTask], [жизненный цикл фоновой задачи],
|
||||
[26], [task-runtime], [failRecordsFailedState], [жизненный цикл фоновой задачи],
|
||||
[27], [task-runtime], [logAppendsLine], [log appends line],
|
||||
[28], [task-runtime], [progressUpdatesRunningState], [жизненный цикл фоновой задачи],
|
||||
[29], [ui], [clearContentProgress_mapsToStringRes], [маршрутизация, deep link или подписи UI],
|
||||
[30], [ui], [mapsFeatureStorageNotFound], [исключение преобразуется в типизированную ошибку Wallenc],
|
||||
[31], [ui], [mapsStorageIncorrectKey], [исключение преобразуется в типизированную ошибку Wallenc],
|
||||
[32], [ui], [mapsUnknown], [исключение преобразуется в типизированную ошибку Wallenc],
|
||||
[33], [ui], [matchesTasksAndSettingsHosts], [matches tasks and settings hosts],
|
||||
[34], [ui], [matchesWallencViewIntent], [маршрутизация, deep link или подписи UI],
|
||||
[35], [ui], [parsesStandardTotpUri], [корректность TOTP/OTP: parses standard totp uri],
|
||||
[36], [ui], [rejectsMissingSecret], [разбор и валидация входных данных],
|
||||
[37], [ui], [rejectsNonOtpauthScheme], [корректность TOTP/OTP: rejects non otpauth scheme],
|
||||
[38], [ui], [rejectsUnrelatedIntent], [разбор и валидация входных данных],
|
||||
[39], [ui], [startTestTaskEnqueuesWork], [постановка тестовой задачи в очередь orchestrator],
|
||||
[40], [ui], [storageHomeRouteCarriesVaultAndStorageIds], [маршрутизация, deep link или подписи UI],
|
||||
[41], [ui], [syncNoGroups_mapsToStringRes], [сценарий синхронизации: no groups_maps to string res],
|
||||
[42], [ui], [textSecretsRoutesCarryRequiredArguments], [маршрутизация, deep link или подписи UI],
|
||||
[43], [ui], [vaultTask_mapsToStringRes], [маршрутизация, deep link или подписи UI],
|
||||
[44], [usecases], [buildTwoFaCodeStateMatchesJavaOtpForKnownSecret], [корректность TOTP/OTP: build two fa code state matches java otp for known secret],
|
||||
[45], [usecases], [buildTwoFaCodeStateReturnsNullForInvalidSecret], [build two fa code state returns null for invalid secret],
|
||||
[46], [usecases], [deleteWithRecordSyncJournalFalseDoesNotBumpSequence], [удаление без записи в журнал не увеличивает sequence],
|
||||
[47], [usecases], [isSyncableUserPathExcludesEncDirAndJournal], [пользовательский путь исключает служебные каталоги],
|
||||
[48], [usecases], [mergeKeepsSingleEntryPerPath], [слияние журнала оставляет одну запись на путь],
|
||||
[49], [usecases], [openReadDoesNotChangeJournal], [чтение без записи не изменяет журнал синхронизации],
|
||||
[50], [usecases], [storageWithEncInfoIsIncompatible], [хранилище с шифрованием несовместимо в одной группе sync],
|
||||
[51], [usecases], [storageWithoutEncInfoIsCompatible], [хранилище без метаданных шифрования совместимо с синхронизацией],
|
||||
[52], [usecases], [syncAllGroupsReportsNoGroupsWhenEmpty], [сценарий синхронизации: all groups reports no groups when empty],
|
||||
[53], [usecases], [syncGroupCooperativeCancellationReleasesLocks], [снятие блокировок при отмене задачи пользователем],
|
||||
[54], [usecases], [syncGroupCopiesFileFromSourceToTarget], [копирование файла с источника на целевое хранилище в группе],
|
||||
[55], [usecases], [syncGroupDeleteRemovesFileOnTarget], [удаление файла на целевом хранилище при синхронизации],
|
||||
[56], [usecases], [syncGroupReleasesLocksAfterSuccessfulSync], [снятие блокировок после успешной синхронизации],
|
||||
[57], [usecases], [syncGroupReleasesLocksWhenJournalEmpty], [снятие блокировок при пустом журнале],
|
||||
[58], [usecases], [syncGroupReleasesLocksWhenJournalReadFails], [снятие блокировок при ошибке чтения журнала],
|
||||
[59], [usecases], [syncGroupSkippedWhenFewerThanTwoStorages], [синхронизация пропускается, если в группе меньше двух хранилищ],
|
||||
[60], [usecases], [syncGroupStopsWhenLockCannotBeAcquired], [остановка при невозможности захватить блокировку группы],
|
||||
[61], [usecases], [syncGroupTrashSoftDeletesOnTarget], [мягкое удаление (trash) на целевом хранилище],
|
||||
[62], [usecases], [syncSkipsWhenTargetRevisionAlreadyWinner], [пропуск синхронизации, если ревизия цели уже новее],
|
||||
[63], [usecases], [textSecretsCrudWorksWithOptionalLabels], [CRUD-операции и сохранение данных],
|
||||
[64], [usecases], [textSecretsInvalidJsonFallsBackToEmptyList], [text secrets invalid json falls back to empty list],
|
||||
[65], [usecases], [totpPeriodProgressIsContinuousWithinPeriod], [корректность TOTP/OTP: totp period progress is continuous within period],
|
||||
[66], [usecases], [totpSecondsUntilRefreshCountsDownWithinPeriod], [корректность TOTP/OTP: totp seconds until refresh counts down within period],
|
||||
[67], [usecases], [twoFaCrudWorksAndPersists], [CRUD-операции и сохранение данных],
|
||||
[68], [usecases], [twoFaInvalidJsonFallsBackToEmptyList], [two fa invalid json falls back to empty list],
|
||||
) <tbl-unit-all>
|
||||
|
||||
=== Реестр тестов модуля :domain
|
||||
|
||||
#pz-test-table(
|
||||
[Unit-тесты модуля :domain],
|
||||
3,
|
||||
table.header(
|
||||
[Класс],
|
||||
[Метод],
|
||||
[Файл],
|
||||
),
|
||||
[EncryptorTest], [test correct key for StorageEncryptionInfo], [EncryptorTest.kt],
|
||||
[EncryptorTest], [test incorrect key for StorageEncryptionInfo], [EncryptorTest.kt],
|
||||
[EncryptorTest], [test string encryption with the same key], [EncryptorTest.kt],
|
||||
[EncryptorTest], [test string encryption with the wrong key], [EncryptorTest.kt],
|
||||
[EncryptorTest], [test bytes encryption with the same key], [EncryptorTest.kt],
|
||||
[EncryptorTest], [test bytes encryption with the wrong key], [EncryptorTest.kt],
|
||||
[EncryptorTest], [test stream encryption with the same key], [EncryptorTest.kt],
|
||||
[EncryptorTest], [test stream encryption with the wrong key], [EncryptorTest.kt],
|
||||
[WallencExceptionMappingTest], [preservesWallencException], [WallencExceptionMappingTest.kt],
|
||||
[WallencExceptionMappingTest], [mapsFileNotFoundException], [WallencExceptionMappingTest.kt],
|
||||
[WallencExceptionMappingTest], [mapsIOExceptionToIoFailed], [WallencExceptionMappingTest.kt],
|
||||
[WallencExceptionMappingTest], [mapsGenericExceptionToUnknown], [WallencExceptionMappingTest.kt],
|
||||
) <tbl-unit-domain>
|
||||
|
||||
=== Реестр тестов модуля :domain-vault
|
||||
|
||||
#pz-test-table(
|
||||
[Unit-тесты модуля :domain-vault],
|
||||
3,
|
||||
table.header(
|
||||
[Класс],
|
||||
[Метод],
|
||||
[Файл],
|
||||
),
|
||||
[VaultThrowableMappingTest], [mapsYandexDiskAuthToAuthFailed], [VaultThrowableMappingTest.kt],
|
||||
[VaultThrowableMappingTest], [mapsHttpExceptionToNetworkHttpFailed], [VaultThrowableMappingTest.kt],
|
||||
[VaultThrowableMappingTest], [mapsMissingOAuthTokenIoToTokenMissing], [VaultThrowableMappingTest.kt],
|
||||
[VaultThrowableMappingTest], [mapsSocketTimeoutToOperationTimedOut], [VaultThrowableMappingTest.kt],
|
||||
[VaultThrowableMappingTest], [mapsFileNotFoundToStorageFileNotFound], [VaultThrowableMappingTest.kt],
|
||||
[VaultThrowableMappingTest], [mapsIllegalStateNotAFile], [VaultThrowableMappingTest.kt],
|
||||
[YandexDiskRepositoryTest], [diskInfoParsesResponse], [YandexDiskRepositoryTest.kt],
|
||||
[YandexDiskRepositoryTest], [listReturnsEmptyEmbeddedOn404], [YandexDiskRepositoryTest.kt],
|
||||
[YandexDiskRepositoryTest], [diskInfoThrowsAuthExceptionOn401], [YandexDiskRepositoryTest.kt],
|
||||
[StorageSyncJournalBufferTest], [flushRestoresPendingOnWriteFailure], [StorageSyncJournalBufferTest.kt],
|
||||
) <tbl-unit-domain_vault>
|
||||
|
||||
=== Реестр тестов модуля :task-runtime
|
||||
|
||||
#pz-test-table(
|
||||
[Unit-тесты модуля :task-runtime],
|
||||
3,
|
||||
table.header(
|
||||
[Класс],
|
||||
[Метод],
|
||||
[Файл],
|
||||
),
|
||||
[TaskOrchestratorTest], [enqueueCompletesTask], [TaskOrchestratorTest.kt],
|
||||
[TaskOrchestratorTest], [cancelAllMarksRunningTaskCancelled], [TaskOrchestratorTest.kt],
|
||||
[TaskOrchestratorTest], [cancelMarksTaskCancelled], [TaskOrchestratorTest.kt],
|
||||
[TaskOrchestratorTest], [failRecordsFailedState], [TaskOrchestratorTest.kt],
|
||||
[TaskOrchestratorTest], [progressUpdatesRunningState], [TaskOrchestratorTest.kt],
|
||||
[TaskOrchestratorTest], [logAppendsLine], [TaskOrchestratorTest.kt],
|
||||
) <tbl-unit-task_runtime>
|
||||
|
||||
=== Реестр тестов модуля :ui
|
||||
|
||||
#pz-test-table(
|
||||
[Unit-тесты модуля :ui],
|
||||
3,
|
||||
table.header(
|
||||
[Класс],
|
||||
[Метод],
|
||||
[Файл],
|
||||
),
|
||||
[WallencDeepLinksTest], [matchesWallencViewIntent], [WallencDeepLinksTest.kt],
|
||||
[WallencDeepLinksTest], [rejectsUnrelatedIntent], [WallencDeepLinksTest.kt],
|
||||
[WallencDeepLinksTest], [matchesTasksAndSettingsHosts], [WallencDeepLinksTest.kt],
|
||||
[TaskProgressLabelsTest], [syncNoGroups_mapsToStringRes], [TaskProgressLabelsTest.kt],
|
||||
[TaskProgressLabelsTest], [vaultTask_mapsToStringRes], [TaskProgressLabelsTest.kt],
|
||||
[TaskProgressLabelsTest], [clearContentProgress_mapsToStringRes], [TaskProgressLabelsTest.kt],
|
||||
[WallencUserNotificationMappingTest], [mapsFeatureStorageNotFound], [WallencUserNotificationMappingTest.kt],
|
||||
[WallencUserNotificationMappingTest], [mapsStorageIncorrectKey], [WallencUserNotificationMappingTest.kt],
|
||||
[WallencUserNotificationMappingTest], [mapsUnknown], [WallencUserNotificationMappingTest.kt],
|
||||
[StorageNavigationRoutesSmokeTest], [storageHomeRouteCarriesVaultAndStorageIds], [StorageNavigationRoutesSmokeTest.kt],
|
||||
[StorageNavigationRoutesSmokeTest], [textSecretsRoutesCarryRequiredArguments], [StorageNavigationRoutesSmokeTest.kt],
|
||||
[OtpAuthUriParserTest], [parsesStandardTotpUri], [OtpAuthUriParserTest.kt],
|
||||
[OtpAuthUriParserTest], [rejectsNonOtpauthScheme], [OtpAuthUriParserTest.kt],
|
||||
[OtpAuthUriParserTest], [rejectsMissingSecret], [OtpAuthUriParserTest.kt],
|
||||
[TaskPipelineViewModelTest], [startTestTaskEnqueuesWork], [TaskPipelineViewModelTest.kt],
|
||||
) <tbl-unit-ui>
|
||||
|
||||
=== Реестр тестов модуля :usecases
|
||||
|
||||
#pz-test-table(
|
||||
[Unit-тесты модуля :usecases],
|
||||
3,
|
||||
table.header(
|
||||
[Класс],
|
||||
[Метод],
|
||||
[Файл],
|
||||
),
|
||||
[StorageDomainUseCasesTest], [twoFaCrudWorksAndPersists], [StorageDomainUseCasesTest.kt],
|
||||
[StorageDomainUseCasesTest], [twoFaInvalidJsonFallsBackToEmptyList], [StorageDomainUseCasesTest.kt],
|
||||
[StorageDomainUseCasesTest], [textSecretsCrudWorksWithOptionalLabels], [StorageDomainUseCasesTest.kt],
|
||||
[StorageDomainUseCasesTest], [textSecretsInvalidJsonFallsBackToEmptyList], [StorageDomainUseCasesTest.kt],
|
||||
[StorageSyncEncryptionCompatTest], [storageWithoutEncInfoIsCompatible], [StorageSyncEncryptionCompatTest.kt],
|
||||
[StorageSyncEncryptionCompatTest], [storageWithEncInfoIsIncompatible], [StorageSyncEncryptionCompatTest.kt],
|
||||
[StorageSyncEngineTest], [syncAllGroupsReportsNoGroupsWhenEmpty], [StorageSyncEngineTest.kt],
|
||||
[StorageSyncEngineTest], [syncGroupCopiesFileFromSourceToTarget], [StorageSyncEngineTest.kt],
|
||||
[StorageSyncEngineTest], [syncGroupSkippedWhenFewerThanTwoStorages], [StorageSyncEngineTest.kt],
|
||||
[StorageSyncEngineTest], [syncGroupDeleteRemovesFileOnTarget], [StorageSyncEngineTest.kt],
|
||||
[StorageSyncEngineTest], [syncSkipsWhenTargetRevisionAlreadyWinner], [StorageSyncEngineTest.kt],
|
||||
[StorageSyncEngineTest], [openReadDoesNotChangeJournal], [StorageSyncEngineTest.kt],
|
||||
[StorageSyncEngineTest], [deleteWithRecordSyncJournalFalseDoesNotBumpSequence], [StorageSyncEngineTest.kt],
|
||||
[StorageSyncEngineTest], [syncGroupTrashSoftDeletesOnTarget], [StorageSyncEngineTest.kt],
|
||||
[StorageSyncEngineTest], [syncGroupStopsWhenLockCannotBeAcquired], [StorageSyncEngineTest.kt],
|
||||
[StorageSyncEngineTest], [syncGroupReleasesLocksAfterSuccessfulSync], [StorageSyncEngineTest.kt],
|
||||
[StorageSyncEngineTest], [syncGroupReleasesLocksWhenJournalReadFails], [StorageSyncEngineTest.kt],
|
||||
[StorageSyncEngineTest], [syncGroupCooperativeCancellationReleasesLocks], [StorageSyncEngineTest.kt],
|
||||
[StorageSyncEngineTest], [syncGroupReleasesLocksWhenJournalEmpty], [StorageSyncEngineTest.kt],
|
||||
[StorageSyncJournalMergeTest], [mergeKeepsSingleEntryPerPath], [StorageSyncJournalMergeTest.kt],
|
||||
[StorageSyncJournalMergeTest], [isSyncableUserPathExcludesEncDirAndJournal], [StorageSyncJournalMergeTest.kt],
|
||||
[TwoFaTotpTest], [buildTwoFaCodeStateMatchesJavaOtpForKnownSecret], [TwoFaTotpTest.kt],
|
||||
[TwoFaTotpTest], [totpPeriodProgressIsContinuousWithinPeriod], [TwoFaTotpTest.kt],
|
||||
[TwoFaTotpTest], [totpSecondsUntilRefreshCountsDownWithinPeriod], [TwoFaTotpTest.kt],
|
||||
[TwoFaTotpTest], [buildTwoFaCodeStateReturnsNullForInvalidSecret], [TwoFaTotpTest.kt],
|
||||
) <tbl-unit-usecases>
|
||||
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
|
||||
= Тестирование программного обеспечения
|
||||
|
||||
Тестирование Wallenc организовано по уровням: модульные автоматические тесты (JUnit, каталог `src/test` каждого Gradle-модуля), инструментальные тесты (`src/androidTest`, эмулятор/устройство), ручные функциональные и UI-прогоны. Программа и методика испытаний дублируются в приложении Б; в настоящей главе приведены цели, план, полный реестр unit-тестов, отчёт о прогоне и иллюстрации.
|
||||
В ходе работы было организовано тестирование Wallenc на нескольких уровнях: модульные автоматические тесты (JUnit, каталог `src/test` каждого Gradle-модуля), инструментальные тесты (`src/androidTest`), а также ручные функциональные и UI-прогоны. Программа и методика испытаний приведены в приложении Б.
|
||||
|
||||
== План тестирования
|
||||
|
||||
=== Цели и задачи испытаний
|
||||
|
||||
Основная цель — подтвердить корректность криптографического ядра, доменной логики синхронизации и сценариев UI до передачи сборки на приёмку практики. Задачи плана:
|
||||
Основная цель — подтвердить корректность криптографического ядра, доменной логики синхронизации и сценариев UI. Были поставлены следующие задачи:
|
||||
|
||||
+ верифицировать `Encryptor` и проверку ключа для всех носителей (строка, байты, поток);
|
||||
+ проверить маппинг исключений в пользовательские коды ошибок (`domain`, `domain-vault`, `ui`);
|
||||
+ убедиться в согласованности движка синхронизации (`StorageSyncEngine`, журнал, блокировки);
|
||||
+ проверить оркестратор фоновых задач (`task-runtime`);
|
||||
+ проверить `Encryptor` и проверку ключа для строк, байтов и потоков;
|
||||
+ убедиться в корректном маппинге исключений в коды ошибок;
|
||||
+ протестировать движок синхронизации (`StorageSyncEngine`, журнал, блокировки);
|
||||
+ проверить оркестратор фоновых задач;
|
||||
+ выполнить smoke-тесты навигации, deep link и 2FA/TOTP;
|
||||
+ зафиксировать результаты ручных сценариев vault, OAuth и экрана задач.
|
||||
|
||||
@@ -23,39 +23,37 @@
|
||||
[Объекты и уровни тестирования Wallenc],
|
||||
4,
|
||||
table.header([Уровень], [Объект], [Инструмент], [Критерий успеха]),
|
||||
[Unit], [Классы domain, usecases, ui, task-runtime, domain-vault], [JUnit 4, JVM], [Все тесты модуля зелёные],
|
||||
[Unit], [Классы domain, usecases, ui, task-runtime, domain-vault], [JUnit 4, JVM], [Все тесты модуля успешны],
|
||||
[Инструм.], [Room, Compose UI, OAuth], [AndroidJUnit, эмулятор], [Нет падений на целевом API],
|
||||
[Ручной], [Сборка app, пользовательские цепочки], [Чек-лист], [Сценарии T-1…T-12 пройдены],
|
||||
[Регресс.], [Синхронизация, шифрование], [Повтор unit + выборочный ручной], [Отсутствие блокирующих дефектов],
|
||||
[Регресс.], [Синхронизация, шифрование], [Повтор unit + выборочный ручной], [Нет блокирующих дефектов],
|
||||
) <tbl-test-levels>
|
||||
|
||||
=== Матрица тестовых сценариев
|
||||
|
||||
Матрица связывает требования (гл. 1) с видами испытаний. Столбец «Автоматизация» указывает, покрыт ли сценарий unit-тестом.
|
||||
|
||||
#pz-table(
|
||||
[Матрица тестовых сценариев],
|
||||
5,
|
||||
table.header([ID], [Сценарий], [Тип], [Авто], [Ожидаемый результат]),
|
||||
[T-1], [Проверка ключа шифрования], [Unit], [Да], [`Encryptor.checkKey` true/false],
|
||||
[T-2], [Шифрование/дешифрование строки и байтов], [Unit], [Да], [Симметрия данных, ошибка при неверном ключе],
|
||||
[T-3], [Потоковое шифрование файла], [Unit], [Да], [Массив после decrypt равен исходному],
|
||||
[T-2], [Шифрование/дешифрование строки и байтов], [Unit], [Да], [Симметрия данных],
|
||||
[T-3], [Потоковое шифрование файла], [Unit], [Да], [Данные после decrypt равны исходным],
|
||||
[T-4], [Синхронизация группы хранилищ], [Unit], [Да], [Копирование, удаление, trash, блокировки],
|
||||
[T-5], [2FA TOTP генерация], [Unit], [Да], [Совпадение с эталоном Java OTP],
|
||||
[T-6], [Маппинг ошибок сети/диска], [Unit], [Да], [Типизированные `WallencException`],
|
||||
[T-7], [CRUD локального vault], [Ручной], [Нет], [Список обновлён (рис. 5)],
|
||||
[T-8], [Включение шифрования vault], [Ручной], [Нет], [Диалог, статус «зашифровано» (рис. 6)],
|
||||
[T-9], [Открытие/закрытие vault], [Ручной], [Нет], [Доступ к содержимому только с ключом (рис. 7)],
|
||||
[T-10], [OAuth Яндекс], [Ручной / IT], [Частично], [Токен в Room (рис. 10)],
|
||||
[T-7], [CRUD локального vault], [Ручной], [Нет], [Список обновлён (рис. @fig-05)],
|
||||
[T-8], [Включение шифрования vault], [Ручной], [Нет], [Статус «зашифровано» (рис. @fig-06)],
|
||||
[T-9], [Открытие/закрытие vault], [Ручной], [Нет], [Доступ только с ключом (рис. @fig-07)],
|
||||
[T-10], [OAuth Яндекс], [Ручной / IT], [Частично], [Токен в Room (рис. @fig-10)],
|
||||
[T-11], [Экран задач и уведомления], [Ручной], [Частично], [Прогресс и завершение (рис. 12–13)],
|
||||
[T-12], [Compose: секреты и 2FA экраны], [IT], [Да], [Отображение без падений (рис. 30)],
|
||||
[T-12], [Compose: секреты и 2FA], [IT], [Да], [Отображение без падений],
|
||||
) <tbl-testplan>
|
||||
|
||||
=== Критерии начала и окончания
|
||||
|
||||
*Начало:* собраны модули `:domain`, `:usecases`, `:ui`, `:domain-vault`, `:task-runtime`, `:app`; на CI/рабочей станции выполняется `./gradlew test`; для инструментальных тестов доступен эмулятор API 26+.
|
||||
*Начало:* собраны модули проекта; выполняется `./gradlew test`; для инструментальных тестов доступен эмулятор API 26+.
|
||||
|
||||
*Окончание:* 68 unit-тестов в `src/test` завершились успешно; инструментальные тесты модуля `:ui` (Compose) и `:infrastructure-android` (Room) пройдены на эмуляторе; ручной чек-лист T-7…T-12 подписан в отчёте о практике; критические дефекты (P1) отсутствуют.
|
||||
*Окончание:* все 68 unit-тестов в `src/test` завершились успешно; инструментальные тесты пройдены на эмуляторе; ручной чек-лист T-7…T-12 выполнен; критические дефекты отсутствуют.
|
||||
|
||||
=== Среда и инструменты
|
||||
|
||||
@@ -63,71 +61,48 @@
|
||||
[Тестовая среда],
|
||||
2,
|
||||
table.header([Параметр], [Значение]),
|
||||
[ОС разработки], [GNU/Linux, Android Studio Narwhal],
|
||||
[JDK], [OpenJDK 17 / 21 (Gradle toolchain)],
|
||||
[ОС разработки], [GNU/Linux, Android Studio],
|
||||
[JDK], [OpenJDK 17 / 21],
|
||||
[Сборка], [`./gradlew test`, `./gradlew connectedDebugAndroidTest`],
|
||||
[Устройство], [Эмулятор Pixel 6 API 34; физическое устройство для OAuth],
|
||||
[Отчёт JVM], [HTML/XML в `build/reports/tests/test` каждого модуля],
|
||||
) <tbl-test-env>
|
||||
|
||||
== Модульные тесты (JUnit)
|
||||
|
||||
В проекте реализовано *68* автоматических unit-тестов в пяти модулях (`:domain` — 12, `:domain-vault` — 10, `:usecases` — 25, `:ui` — 15, `:task-runtime` — 6). Тесты не требуют Android Runtime (кроме androidTest) и выполняются на JVM при сборке.
|
||||
|
||||
Сводная таблица всех методов приведена ниже; по модулям — в подразделах.
|
||||
В проекте реализовано 68 автоматических unit-тестов в пяти модулях (`:domain` — 12, `:domain-vault` — 10, `:usecases` — 25, `:ui` — 15, `:task-runtime` — 6). Тесты выполняются на JVM при сборке.
|
||||
|
||||
#include "ch05-tests-generated.typ"
|
||||
|
||||
=== Модуль :domain — криптография и ошибки
|
||||
=== Криптография и доменные ошибки
|
||||
|
||||
Класс `EncryptorTest` покрывает восемь сценариев AES: проверка ключа, шифрование строк, байтовых массивов (512 байт) и потоков (1500 байт) с верным и неверным ключом. Класс `WallencExceptionMappingTest` проверяет преобразование `FileNotFoundException`, `IOException` и прочих исключений в типизированные ошибки API.
|
||||
|
||||
#pz-table(
|
||||
[Покрытие EncryptorTest],
|
||||
3,
|
||||
table.header([Метод], [Проверяемое поведение], [Статус]),
|
||||
[test correct key…], [`checkKey` возвращает true для сгенерированного `StorageEncryptionInfo`], [OK],
|
||||
[test incorrect key…], [`checkKey` false при другом `EncryptKey`], [OK],
|
||||
[test string encryption…], [Симметрия encrypt/decrypt строки], [OK],
|
||||
[test string encryption wrong key], [Исключение при decrypt], [OK],
|
||||
[test bytes encryption…], [Шифротекст ≠ plaintext, decrypt восстанавливает], [OK],
|
||||
[test bytes encryption wrong key], [Исключение при неверном ключе], [OK],
|
||||
[test stream encryption…], [Поток 1500 байт, `readAllBytes` совпадает], [OK],
|
||||
[test stream encryption wrong key], [Исключение на decryptStream], [OK],
|
||||
) <tbl-encryptor>
|
||||
Класс `EncryptorTest` проверяет сценарии AES: `checkKey`, шифрование строк, байтовых массивов и потоков с верным и неверным ключом (строки 5–14 табл. @tbl-unit-all). `WallencExceptionMappingTest` покрывает преобразование файловых и сетевых исключений.
|
||||
|
||||
Прогон `./gradlew :domain:test` — на рис. @fig-27.
|
||||
|
||||
#include "ch05-encryptor.typ"
|
||||
|
||||
#pz-fig("fig_27_gradle_domain_test.png", [Отчёт Gradle: модуль :domain, задача test], "fig-27")
|
||||
|
||||
=== Модуль :usecases — синхронизация, 2FA, секреты
|
||||
=== Синхронизация, 2FA и use cases
|
||||
|
||||
Наибольшая плотность тестов: `StorageSyncEngineTest` (12 методов) моделирует группы синхронизации, копирование и удаление файлов, soft-delete (trash), cooperative cancellation и освобождение блокировок. `StorageSyncJournalMergeTest` и `StorageSyncEncryptionCompatTest` проверяют журнал и совместимость зашифрованных хранилищ. `TwoFaTotpTest` сверяет TOTP с эталоном Java OTP. `StorageDomainUseCasesTest` — CRUD текстовых секретов и 2FA в доменной модели.
|
||||
|
||||
#include "ch05-sync-tests.typ"
|
||||
`StorageSyncEngineTest` моделирует группы синхронизации, копирование и удаление файлов, soft-delete, отмену и блокировки (строки 52–64 табл. @tbl-unit-all). `TwoFaTotpTest` сверяет TOTP с эталоном Java OTP. `StorageDomainUseCasesTest` проверяет CRUD текстовых секретов и 2FA.
|
||||
|
||||
#pz-fig("fig_28_gradle_usecases_test.png", [Отчёт Gradle: модуль :usecases], "fig-28")
|
||||
|
||||
=== Модуль :domain-vault — Yandex Disk и vault
|
||||
=== Модуль :domain-vault
|
||||
|
||||
`YandexDiskRepositoryTest` использует мок HTTP: разбор `diskInfo`, пустой список при 404, `AuthException` при 401. `VaultThrowableMappingTest` — шесть веток сетевых и файловых ошибок. `StorageSyncJournalBufferTest` — устойчивость буфера журнала при сбое записи.
|
||||
`YandexDiskRepositoryTest` использует мок HTTP: разбор `diskInfo`, пустой список при 404, `AuthException` при 401. `VaultThrowableMappingTest` покрывает сетевые и файловые ошибки vault.
|
||||
|
||||
=== Модуль :ui — навигация, уведомления, OTP
|
||||
=== Модуль :ui
|
||||
|
||||
Тесты не поднимают полный Compose, а проверяют чистые функции: `WallencDeepLinksTest`, `OtpAuthUriParserTest`, `TaskProgressLabelsTest`, `WallencUserNotificationMappingTest`, `StorageNavigationRoutesSmokeTest`, `TaskPipelineViewModelTest` (постановка тестовой задачи в очередь).
|
||||
Проверены чистые функции навигации, deep link, подписи уведомлений, парсинг OTP URI и постановка задачи в очередь (`TaskPipelineViewModelTest`).
|
||||
|
||||
#pz-fig("fig_29_gradle_ui_test.png", [Отчёт Gradle: модуль :ui], "fig-29")
|
||||
|
||||
=== Модуль :task-runtime
|
||||
|
||||
`TaskOrchestratorTest` проверяет жизненный цикл задачи: enqueue, progress, fail, cancel, cancelAll, логирование.
|
||||
`TaskOrchestratorTest` проверяет enqueue, progress, fail, cancel и cancelAll.
|
||||
|
||||
== Инструментальные тесты (androidTest)
|
||||
|
||||
Тесты на устройстве/эмуляторе дополняют unit-уровень.
|
||||
|
||||
#pz-table(
|
||||
[Инструментальные тесты androidTest],
|
||||
4,
|
||||
@@ -136,66 +111,48 @@
|
||||
[:ui], [TextSecretsScreenContentTest], [Compose: текстовые секреты], [2],
|
||||
[:infra], [YandexAccountRepositoryTest], [Room in-memory: аккаунт Яндекс], [3],
|
||||
[:app], [YandexDiskLiveIntegrationTest], [Живой API (при наличии токена)], [3],
|
||||
[:app], [ExportYandexTestCredentialsTest], [Экспорт тестовых учётных данных], [1],
|
||||
) <tbl-androidtest>
|
||||
|
||||
Запуск: `./gradlew connectedDebugAndroidTest`. Сводный результат — рис. @fig-31.
|
||||
Запуск: `./gradlew connectedDebugAndroidTest`. Результат — рис. @fig-31.
|
||||
|
||||
#pz-fig("fig_31_gradle_connected_test.png", [Gradle connectedDebugAndroidTest (фрагмент)], "fig-31")
|
||||
#pz-fig("fig_31_gradle_connected_test.png", [Gradle connectedDebugAndroidTest], "fig-31")
|
||||
|
||||
== Ручное и UI-тестирование
|
||||
|
||||
Ручные прогоны выполнялись по чек-листу T-7…T-12 на эмуляторе и физическом устройстве. Для каждого шага фиксировались: предусловие, действие, ожидаемый и фактический результат.
|
||||
Ручные прогоны выполнялись по чек-листу T-7…T-12 на эмуляторе и физическом устройстве.
|
||||
|
||||
#pz-table(
|
||||
[Протокол ручного тестирования],
|
||||
5,
|
||||
table.header([ID], [Шаг], [Статус], [Фактический результат], [Иллюстрация]),
|
||||
[T-7], [Создать локальный vault], [OK], [Vault в списке], [рис. 5],
|
||||
[T-8], [Включить шифрование], [OK], [Статус encrypted], [рис. 6],
|
||||
[T-9], [Открыть/закрыть vault], [OK], [Контент доступен только открытому], [рис. 7],
|
||||
[T-10], [OAuth Яндекс], [OK], [Запись в `DbYandexAccount`], [рис. 10],
|
||||
[T-7], [Создать локальный vault], [OK], [Vault в списке], [@fig-05],
|
||||
[T-8], [Включить шифрование], [OK], [Статус encrypted], [@fig-06],
|
||||
[T-9], [Открыть/закрыть vault], [OK], [Контент только при открытом vault], [@fig-07],
|
||||
[T-10], [OAuth Яндекс], [OK], [Запись в `DbYandexAccount`], [@fig-10],
|
||||
[T-11], [Фоновая задача шифрования], [OK], [Прогресс на экране задач], [рис. 12],
|
||||
[T-12], [Уведомление о завершении], [OK], [Notification отображён], [рис. 13],
|
||||
) <tbl-testres>
|
||||
|
||||
#pz-fig("fig_32_manual_test_checklist.png", [Заполненный чек-лист ручного UI-тестирования], "fig-32")
|
||||
#pz-fig("fig_32_manual_test_checklist.png", [Чек-лист ручного UI-тестирования], "fig-32")
|
||||
|
||||
== Отчёт о результатах тестирования
|
||||
|
||||
По итогам автоматического прогона `./gradlew test` все 68 unit-тестов завершились со статусом *PASSED*. Регрессия по криптографии и синхронизации не выявила отклонений. Инструментальные тесты `:ui` подтвердили отрисовку экранов секретов и 2FA; тесты Room — персистентность учётной записи Яндекс.
|
||||
По итогам `./gradlew test` все 68 unit-тестов завершились со статусом PASSED. Инструментальные тесты `:ui` подтвердили отрисовку экранов секретов и 2FA; тесты Room — персистентность учётной записи Яндекс.
|
||||
|
||||
#pz-fig("fig_30_gradle_test_summary.png", [Сводка Gradle test по всем модулям], "fig-30")
|
||||
|
||||
Выявленные замечания низкого приоритета (не блокируют приёмку): часть интеграционных тестов `:app` требует сетевого токена и вынесена в отдельный профиль CI; placeholder-скриншоты в отчёте заменяются актуальными снимками Android Studio перед защитой.
|
||||
|
||||
#pz-table(
|
||||
[Классификация дефектов по итогам практики],
|
||||
4,
|
||||
table.header([ID], [Приоритет], [Описание], [Статус]),
|
||||
[D-1], [P3], [Дублирование подписи листинга в приложении А (оформление)], [Исправлено],
|
||||
[D-2], [P3], [Перенос строк в широких таблицах ПЗ], [Исправлено],
|
||||
[D-3], [P2], [Полная синхронизация с облаком — в разработке], [Открыт],
|
||||
) <tbl-defects>
|
||||
|
||||
=== Связь тестов с требованиями
|
||||
#pz-fig("fig_30_gradle_test_summary.png", [Сводка Gradle test по модулям], "fig-30")
|
||||
|
||||
#pz-table(
|
||||
[Трассировка требований → тесты],
|
||||
3,
|
||||
table.header([ФР], [Тесты], [Комментарий]),
|
||||
[ФР-1], [T-7, StorageDomainUseCasesTest], [Локальный vault и CRUD секретов],
|
||||
[ФР-2], [EncryptorTest, T-8, T-9], [Полное покрытие AES],
|
||||
[ФР-2], [EncryptorTest, T-8, T-9], [Покрытие AES],
|
||||
[ФР-3], [TextSecretsScreenContentTest], [UI + domain],
|
||||
[ФР-4], [YandexDiskRepositoryTest, T-10], [HTTP-мок и ручной OAuth],
|
||||
[ФР-5], [StorageSyncEngineTest], [12 сценариев синхронизации],
|
||||
[ФР-5], [StorageSyncEngineTest], [Синхронизация групп],
|
||||
[ФР-6], [TaskOrchestratorTest, T-11], [Очередь и экран задач],
|
||||
) <tbl-trace>
|
||||
|
||||
=== Рекомендации по сопровождению тестов
|
||||
|
||||
При изменении криптомодуля обязателен прогон `:domain:test`. При правках синхронизации — `:usecases:test`. Перед релизом — полный `./gradlew test` и выборочный `connectedDebugAndroidTest`. Регрессионный чек-лист T-7…T-12 выполняется после изменений в Compose-экранах vault. Скрипт `Report/scripts/gen_test_tables.py` обновляет реестр тестов в ПЗ при добавлении новых `@Test`.
|
||||
|
||||
== Вывод
|
||||
|
||||
План тестирования выполнен: автоматизированное покрытие охватывает криптографию, синхронизацию, задачи, парсинг OTP и обработку ошибок; ручные сценарии подтвердили пригодность UI для vault и OAuth. Совокупность испытаний обосновывает готовность прототипа Wallenc к демонстрации и развитию в рамках ВКР.
|
||||
План тестирования выполнен: автоматизированное покрытие охватывает криптографию, синхронизацию, задачи, парсинг OTP и обработку ошибок; ручные сценарии подтвердили пригодность UI для vault и OAuth. Результаты обосновывают готовность прототипа Wallenc к демонстрации и развитию в рамках ВКР.
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#show table: set text(hyphenate: true)
|
||||
|
||||
#let pz-appendix-title(body) = heading(level: 1)[#body]
|
||||
|
||||
#let pz-table(caption, columns, ..body) = figure(
|
||||
table(
|
||||
columns: columns,
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
|
||||
В пояснительной записке рассмотрены анализ предметной области, проектирование и реализация мобильного приложения Wallenc — клиентского кошелька для безопасного хранения данных на недоверенных хранилищах без собственного сервера.
|
||||
|
||||
По главе 1 сформированы требования и выполнен сравнительный анализ аналогов; обоснован выбор стека Kotlin/Compose/Room/Hilt. По главе 2 спроектированы бизнес-процессы, DFD, UML-диаграммы и модель данных Room. Глава 3 описывает пользовательские сценарии и интерфейсные решения. Глава 4 представляет реализованные модули и отсылает к полному листингу в приложении А. Глава 5 документирует план и результаты тестирования. Глава 6 содержит краткую экономическую оценку.
|
||||
По главе 1 сформированы требования и выполнен сравнительный анализ аналогов; обоснован выбор стека Kotlin/Compose/Room/Hilt. По главе 2 спроектированы бизнес-процессы, DFD, UML-диаграммы и модель данных Room. Глава 3 описывает пользовательские сценарии и интерфейсные решения. Глава 4 представляет реализованные модули; полный исходный код приведён в приложении А. Глава 5 документирует план и результаты тестирования. Глава 6 содержит краткую экономическую оценку.
|
||||
|
||||
*Цель работы достигнута*: разработан и протестирован прототип Android-приложения с клиентским шифрованием, управлением vault, OAuth Яндекс и проектным контуром синхронизации.
|
||||
|
||||
*Перспективы развития*: завершение синхронизации по модели коммитов; поддержка дополнительных провайдеров; расширение автоматизированных UI-тестов; оформление акта внедрения (приложение Д при наличии).
|
||||
*Перспективы развития*: завершение синхронизации по модели коммитов; поддержка дополнительных провайдеров; расширение автоматизированных UI-тестов.
|
||||
|
||||
Программная документация (ТЗ, руководство пользователя, материалы испытаний) приведена в приложении Б; иллюстрации интерфейса — в приложении В; диаграммы — в приложении Г.
|
||||
Программная документация приведена в приложении Б; иллюстрации интерфейса — в приложении В.
|
||||
|
||||
По тестированию подтверждено: 68 модульных unit-тестов в `src/test` (модули `:domain`, `:domain-vault`, `:usecases`, `:ui`, `:task-runtime`), инструментальные тесты Compose и Room, ручной протокол из двенадцати сценариев. Отчёты Gradle (рис. 27–31) и чек-лист UI (рис. 32) включены в гл. 5.
|
||||
|
||||
Дальнейшие шаги: завершение синхронизации с облаком; расширение androidTest для OAuth без ручного ввода; публикация актуальных скриншотов вместо учебных заглушек; подготовка акта внедрения (прил. Д) при эксплуатации в Нейротех.
|
||||
По тестированию подтверждено: 68 модульных unit-тестов, инструментальные тесты Compose и Room, ручной протокол из двенадцати сценариев (гл. 5).
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
Современные пользователи хранят личные и рабочие данные в облачных сервисах и на съёмных носителях, однако инфраструктура провайдера не всегда может считаться доверенной. Утечки, компрометация учётных записей и юрисдикционные риски делают актуальным подход, при котором конфиденциальность обеспечивается на стороне клиента до размещения данных во внешнем хранилище @nist-aes @clean-arch.
|
||||
|
||||
*Актуальность* темы обусловлена распространением мобильных приложений для хранения файлов и секретов, а также ограниченностью готовых решений: многие продукты привязаны к собственному backend, закрытой экосистеме или узкой предметной области (менеджеры паролей, локальные «сейфы») @google-secure-folder @bitwarden-help @cryptomator-docs.
|
||||
*Актуальность* темы обусловлена распространением мобильных приложений для хранения файлов и секретов, а также ограниченностью готовых решений: многие продукты привязаны к собственному backend, закрытой экосистеме или узкой предметной области @google-secure-folder @bitwarden-help @cryptomator-docs.
|
||||
|
||||
*Цель работы* — повысить конфиденциальность пользовательских данных при работе с недоверенными хранилищами за счёт разработки мобильного клиентского приложения Wallenc, не требующего развёртывания собственного сервера приложения и обеспечивающего единую модель vault с клиентским шифрованием.
|
||||
*Цель работы* — повысить конфиденциальность пользовательских данных при работе с недоверенными хранилищами за счёт разработки мобильного клиентского приложения Wallenc без собственного сервера приложения и с единой моделью vault и клиентским шифрованием.
|
||||
|
||||
Для достижения цели поставлены следующие *задачи*:
|
||||
Для достижения цели были поставлены следующие *задачи*:
|
||||
+ выполнить анализ предметной области и сравнительный обзор аналогов, сформировать требования к программному продукту;
|
||||
+ спроектировать архитектуру системы, модель данных и пользовательские сценарии;
|
||||
+ реализовать программные модули приложения Wallenc на платформе Android (Kotlin);
|
||||
@@ -17,16 +17,10 @@
|
||||
|
||||
*Методы исследования*: анализ нормативной и технической документации, сравнительный анализ программных аналогов, объектно-ориентированное проектирование (UML, BPMN, DFD), прототипирование пользовательского интерфейса, программная реализация и тестирование @gost7322017 @kotlin-docs.
|
||||
|
||||
*Практическая база.* Работа выполнена в рамках производственной (технологической) практики в ООО НМФ «Нейротех» (09.02.2026–06.05.2026) по направлению 09.03.04 «Программная инженерия», профиль «Методы и средства разработки программного обеспечения». Научный руководитель от университета — Беликов А. Н.; руководитель от организации — Алексеев Д. М.
|
||||
*Практическая база.* Работа выполнена в рамках производственной практики в ООО НМФ «Нейротех» (09.02.2026–06.05.2026) по направлению 09.03.04 «Программная инженерия». Научный руководитель — Беликов А. Н.; руководитель от организации — Алексеев Д. М.
|
||||
|
||||
*Научная новизна* заключается в сочетании универсальной модели vault, клиентского шифрования и адаптерного доступа к разным типам хранилищ без собственного сервера приложения, с проектным контуром синхронизации зашифрованных данных без передачи ключей провайдеру.
|
||||
|
||||
*Практическая значимость*: результаты могут использоваться при дальнейшей разработке продукта в ООО НМФ «Нейротех» и в учебных проектах по мобильной разработке и информационной безопасности. *Апробация* — прохождение производственной практики (09.02.2026–06.05.2026) с реализацией и тестированием рабочей сборки приложения.
|
||||
*Практическая значимость* — использование результатов при дальнейшей разработке продукта и в учебных проектах по мобильной разработке и информационной безопасности.
|
||||
|
||||
*Исходный код* размещён в приватном репозитории Gitea ЮФУ @wallenc-repo; доступ для государственной экзаменационной комиссии предоставляется по запросу научного руководителя.
|
||||
|
||||
*Методика разработки.* Проект вёлся итерациями, согласованными с этапами практики: аналитика и ТЗ; проектирование UML и БД; реализация ядра vault и UI; наращивание автоматических тестов (68 unit-тестов) и ручная приёмка; оформление ПЗ и программной документации. Контроль качества — непрерывный: `./gradlew test` после изменений в `:domain` и `:usecases`, регрессия UI — по чек-листу гл. 5.
|
||||
|
||||
*Содержание глав.* В главе 1 обоснована актуальность клиентского шифрования и приведено сравнение аналогов. Глава 2 описывает архитектуру, DFD, BPMN и модель Room. Глава 3 посвящена UX, User Story и диаграммам потоков. Глава 4 раскрывает реализацию по модулям Gradle. Глава 5 содержит развёрнутый план тестирования, реестр всех unit-тестов, отчёт о прогоне и скриншоты Gradle/Android Studio. Глава 6 даёт краткую экономическую оценку.
|
||||
|
||||
*Приложения.* *Приложение А* — полный листинг исходных файлов (307 файлов, автогенерация). *Приложение Б* — ТЗ, программа испытаний, руководство пользователя. *Приложение В* — скриншоты UI. *Приложение Г* — диаграммы. На приложения даны ссылки в гл. 4–5 и в настоящем введении.
|
||||
Пояснительная записка состоит из введения, шести глав, заключения, списка использованных источников и трёх приложений (листинги исходного кода, программная документация, скриншоты интерфейса). В главе 1 обоснована актуальность и приведено сравнение аналогов; глава 2 описывает архитектуру и модель Room; глава 3 — UX и пользовательские сценарии; глава 4 — реализацию по модулям; глава 5 — тестирование; глава 6 — экономическую оценку.
|
||||
|
||||
32
Report/includes/listings-appendix.typ
Normal file
32
Report/includes/listings-appendix.typ
Normal file
@@ -0,0 +1,32 @@
|
||||
// Show-правила листингов приложения А (вставляются в appendix-a.typ при генерации).
|
||||
|
||||
#show raw: set text(font: "DejaVu Sans Mono")
|
||||
|
||||
#let pz-listing-num-outset = 30mm
|
||||
#let pz-listing-code-indent = 1.25cm
|
||||
|
||||
#show figure.where(supplement: [Листинг]): set block(breakable: true)
|
||||
|
||||
#show raw.where(block: true): it => block(
|
||||
breakable: true,
|
||||
width: 100% + pz-listing-num-outset,
|
||||
outset: (left: pz-listing-num-outset),
|
||||
)[
|
||||
#set block(spacing: 0pt)
|
||||
#it
|
||||
]
|
||||
|
||||
#show raw.line: it => grid(
|
||||
columns: (1.5em, 1fr),
|
||||
column-gutter: pz-listing-code-indent,
|
||||
align: (right + horizon, left + horizon),
|
||||
inset: (y: 0.35pt),
|
||||
text(size: 8.5pt, fill: luma(120))[#str(it.number)],
|
||||
box(width: 100%)[
|
||||
#set text(size: 9pt)
|
||||
#set par(leading: 0.45em)
|
||||
#it.body
|
||||
],
|
||||
)
|
||||
|
||||
#set figure(gap: 0.35em)
|
||||
Reference in New Issue
Block a user