Отличное форматирование

This commit is contained in:
2026-05-26 14:59:25 +03:00
parent 2b139a18b3
commit e3a615cb50
49 changed files with 1294 additions and 501 deletions

View File

@@ -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.

View File

@@ -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"

View File

@@ -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` выше.

View File

@@ -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` дополняют движок проверкой слияния журнала и совместимости шифрования в группе.

View File

@@ -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>

View File

@@ -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], [Экран задач и уведомления], [Ручной], [Частично], [Прогресс и завершение (рис. 1213)],
[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`, шифрование строк, байтовых массивов и потоков с верным и неверным ключом (строки 514 табл. @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, отмену и блокировки (строки 5264 табл. @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 к демонстрации и развитию в рамках ВКР.

View File

@@ -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,

View File

@@ -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 (рис. 2731) и чек-лист UI (рис. 32) включены в гл. 5.
Дальнейшие шаги: завершение синхронизации с облаком; расширение androidTest для OAuth без ручного ввода; публикация актуальных скриншотов вместо учебных заглушек; подготовка акта внедрения (прил. Д) при эксплуатации в Нейротех.
По тестированию подтверждено: 68 модульных unit-тестов, инструментальные тесты Compose и Room, ручной протокол из двенадцати сценариев (гл. 5).

View File

@@ -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.202606.05.2026) по направлению 09.03.04 «Программная инженерия», профиль «Методы и средства разработки программного обеспечения». Научный руководитель от университета Беликов А. Н.; руководитель от организации Алексеев Д. М.
*Практическая база.* Работа выполнена в рамках производственной практики в ООО НМФ «Нейротех» (09.02.202606.05.2026) по направлению 09.03.04 «Программная инженерия». Научный руководитель Беликов А. Н.; руководитель от организации Алексеев Д. М.
*Научная новизна* заключается в сочетании универсальной модели vault, клиентского шифрования и адаптерного доступа к разным типам хранилищ без собственного сервера приложения, с проектным контуром синхронизации зашифрованных данных без передачи ключей провайдеру.
*Практическая значимость*: результаты могут использоваться при дальнейшей разработке продукта в ООО НМФ «Нейротех» и в учебных проектах по мобильной разработке и информационной безопасности. *Апробация* прохождение производственной практики (09.02.202606.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. *Приложение Г* диаграммы. На приложения даны ссылки в гл. 45 и в настоящем введении.
Пояснительная записка состоит из введения, шести глав, заключения, списка использованных источников и трёх приложений (листинги исходного кода, программная документация, скриншоты интерфейса). В главе 1 обоснована актуальность и приведено сравнение аналогов; глава 2 описывает архитектуру и модель Room; глава 3 UX и пользовательские сценарии; глава 4 реализацию по модулям; глава 5 тестирование; глава 6 экономическую оценку.

View 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)