M0: 안전 저장소를 못 읽으면 마스터 잠금을 '알 수 없음' 으로 - 문을 열지 않고 마스터를 덮어쓰지 않는다
사용자가 보게 될 동작: 기기 안전 저장소를 읽지 못하면 저장된 비밀번호를 쓰는 기능이 열리지 않고, 환경 설정 > 보안에 까닭과 [다시 읽기] 가 보인다. 다시 읽히면 평소대로 마스터를 묻는다. - 예전: 읽지 못하면 빈 것으로 보아 마스터가 없는 것처럼 문 (SecretGate) 이 열렸고, 그 상태에서 마스터를 정하면 진짜 값을 덮어쓸 수 있었다 - MasterLock.storeUnreadable: load 가 예외 대신 표시 (앱은 켜짐). ensure 는 한 번 다시 읽고 못 읽으면 false. setMaster · setSuper · clearMaster · clearSuper · setAsk 는 MasterStoreUnreadable. reload() - 보안 설정: 까닭 · [다시 읽기] (정하기 · 바꾸기 · 끄기 숨김). 보관함 [마스터로도 열기] 는 까닭을 알림 - 비밀번호 다시 저장 [다시 시도] 가 되면 마스터 잠금도 다시 읽음. settings_store 의 TODO(M0) 지움 - 약속 시험 master_store_unreadable_test 4 (읽기가 실제로 실패하는 저장소를 시험 파일 안에 - lib 흉내 플래그 쓰지 않음) - 확인: 단위 1467 ×2 · 통합 6/6 ×2 · 캡처 214장 base10 과 같음 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>modular
parent
6f9d8f6284
commit
39d81f1a73
|
|
@ -1062,6 +1062,7 @@
|
|||
"다시 시작": "Restart",
|
||||
"다시 쓰려면 다시 받아야 합니다.": "You'll need to download it again to use it.",
|
||||
"다시 열기": "Open again",
|
||||
"다시 읽기": "Read again",
|
||||
"다시 찾기": "Search again",
|
||||
"다시 켤 때 동기화": "Sync on restart",
|
||||
"다시 확인": "Check again",
|
||||
|
|
@ -1245,6 +1246,7 @@
|
|||
"마그넷 · 토렌트가 시작되지 않으면 알리기": "Notify when a magnet / torrent doesn't start",
|
||||
"마스터 · 최상 비밀번호": "Master and super passwords",
|
||||
"마스터 비밀번호": "Master password",
|
||||
"마스터 비밀번호 - 안전 저장소를 읽지 못함": "Master password - cannot read secure storage",
|
||||
"마스터 비밀번호 넣기": "Enter master password",
|
||||
"마스터 비밀번호 정하기": "Set master password",
|
||||
"마스터 비밀번호가 필요합니다": "The master password is needed",
|
||||
|
|
@ -2237,6 +2239,8 @@
|
|||
"이 기기의 CPU 가 이 AI 엔진을 지원하지 않습니다": "This device's CPU does not support this AI engine",
|
||||
"이 기기의 Wi-Fi 주소를 찾지 못했습니다. Wi-Fi 에 연결돼 있는지 확인하세요.": "Couldn't find this device's Wi-Fi address. Check that it is connected to Wi-Fi.",
|
||||
"이 기기의 안전 저장소를 쓸 수 없어 비밀번호를 저장하지 못했습니다": "Couldn't save the password because this device's secure storage is unavailable",
|
||||
"이 기기의 안전 저장소를 읽지 못해 마스터 비밀번호가 있는지 알 수 없습니다. 그동안 저장된 비밀번호를 쓰는 기능은 열지 않고, 마스터 · 최상 비밀번호는 바꿀 수 없습니다.": "This device's secure storage could not be read, so it is unknown whether a master password is set. Until then, features that use saved passwords stay locked and the master and super passwords cannot be changed.",
|
||||
"이 기기의 안전 저장소를 읽지 못해 지금은 켤 수 없습니다 (환경 설정 > 보안 [다시 읽기])": "Cannot turn on now because this device's secure storage could not be read (Settings > Security [Read again])",
|
||||
"이 기기의 웹 뷰로는 Tor 화면을 열 수 없습니다": "The Tor screen cannot be opened with this device's web view",
|
||||
"이 기기의 폴더": "Folder on this device",
|
||||
"이 단축키를 등록할 수 없습니다 (다른 프로그램이 사용 중일 수 있음)": "Cannot register this hotkey (another program may be using it)",
|
||||
|
|
|
|||
|
|
@ -1062,6 +1062,7 @@
|
|||
"다시 시작": "再起動",
|
||||
"다시 쓰려면 다시 받아야 합니다.": "再び使うには再ダウンロードが必要です。",
|
||||
"다시 열기": "もう一度開く",
|
||||
"다시 읽기": "もう一度読む",
|
||||
"다시 찾기": "再検索",
|
||||
"다시 켤 때 동기화": "再起動時の同期",
|
||||
"다시 확인": "再確認",
|
||||
|
|
@ -1245,6 +1246,7 @@
|
|||
"마그넷 · 토렌트가 시작되지 않으면 알리기": "マグネット・トレントが始まらないときに知らせる",
|
||||
"마스터 · 최상 비밀번호": "マスター / 最上位パスワード",
|
||||
"마스터 비밀번호": "マスターパスワード",
|
||||
"마스터 비밀번호 - 안전 저장소를 읽지 못함": "マスターパスワード - 安全な保存領域を読めません",
|
||||
"마스터 비밀번호 넣기": "マスターパスワードを入力",
|
||||
"마스터 비밀번호 정하기": "マスターパスワードを設定",
|
||||
"마스터 비밀번호가 필요합니다": "マスターパスワードが必要です",
|
||||
|
|
@ -2237,6 +2239,8 @@
|
|||
"이 기기의 CPU 가 이 AI 엔진을 지원하지 않습니다": "この端末の CPU はこの AI エンジンに対応していません",
|
||||
"이 기기의 Wi-Fi 주소를 찾지 못했습니다. Wi-Fi 에 연결돼 있는지 확인하세요.": "この端末の Wi-Fi アドレスが見つかりません。Wi-Fi に接続しているか確認してください。",
|
||||
"이 기기의 안전 저장소를 쓸 수 없어 비밀번호를 저장하지 못했습니다": "この端末の安全な保存領域を使えないため、パスワードを保存できませんでした",
|
||||
"이 기기의 안전 저장소를 읽지 못해 마스터 비밀번호가 있는지 알 수 없습니다. 그동안 저장된 비밀번호를 쓰는 기능은 열지 않고, 마스터 · 최상 비밀번호는 바꿀 수 없습니다.": "この端末の安全な保存領域を読めないため、マスターパスワードがあるか分かりません。その間、保存したパスワードを使う機能は開かず、マスター · 最上位パスワードは変更できません。",
|
||||
"이 기기의 안전 저장소를 읽지 못해 지금은 켤 수 없습니다 (환경 설정 > 보안 [다시 읽기])": "この端末の安全な保存領域を読めないため、今はオンにできません (設定 > セキュリティ [もう一度読む])",
|
||||
"이 기기의 웹 뷰로는 Tor 화면을 열 수 없습니다": "この端末の Web ビューでは Tor 画面を開けません",
|
||||
"이 기기의 폴더": "この端末のフォルダー",
|
||||
"이 단축키를 등록할 수 없습니다 (다른 프로그램이 사용 중일 수 있음)": "このショートカットを登録できません(他のプログラムが使用中の可能性があります)",
|
||||
|
|
|
|||
|
|
@ -1062,6 +1062,7 @@
|
|||
"다시 시작": "重新启动",
|
||||
"다시 쓰려면 다시 받아야 합니다.": "再次使用需要重新下载。",
|
||||
"다시 열기": "再次打开",
|
||||
"다시 읽기": "重新读取",
|
||||
"다시 찾기": "重新查找",
|
||||
"다시 켤 때 동기화": "重新启动时同步",
|
||||
"다시 확인": "重新检查",
|
||||
|
|
@ -1245,6 +1246,7 @@
|
|||
"마그넷 · 토렌트가 시작되지 않으면 알리기": "磁力链接 · 种子无法开始时提醒",
|
||||
"마스터 · 최상 비밀번호": "主密码和最高密码",
|
||||
"마스터 비밀번호": "主密码",
|
||||
"마스터 비밀번호 - 안전 저장소를 읽지 못함": "主密码 - 无法读取安全存储",
|
||||
"마스터 비밀번호 넣기": "输入主密码",
|
||||
"마스터 비밀번호 정하기": "设置主密码",
|
||||
"마스터 비밀번호가 필요합니다": "需要主密码",
|
||||
|
|
@ -2237,6 +2239,8 @@
|
|||
"이 기기의 CPU 가 이 AI 엔진을 지원하지 않습니다": "此设备的 CPU 不支持该 AI 引擎",
|
||||
"이 기기의 Wi-Fi 주소를 찾지 못했습니다. Wi-Fi 에 연결돼 있는지 확인하세요.": "找不到本设备的 Wi-Fi 地址。请检查是否已连接 Wi-Fi。",
|
||||
"이 기기의 안전 저장소를 쓸 수 없어 비밀번호를 저장하지 못했습니다": "本设备的安全存储不可用,无法保存密码",
|
||||
"이 기기의 안전 저장소를 읽지 못해 마스터 비밀번호가 있는지 알 수 없습니다. 그동안 저장된 비밀번호를 쓰는 기능은 열지 않고, 마스터 · 최상 비밀번호는 바꿀 수 없습니다.": "无法读取此设备的安全存储,因此不知道是否设置了主密码。在此期间,使用已保存密码的功能保持锁定,主密码和最高密码无法更改。",
|
||||
"이 기기의 안전 저장소를 읽지 못해 지금은 켤 수 없습니다 (환경 설정 > 보안 [다시 읽기])": "无法读取此设备的安全存储,暂时无法开启 (设置 > 安全 [重新读取])",
|
||||
"이 기기의 웹 뷰로는 Tor 화면을 열 수 없습니다": "此设备的网页视图无法打开 Tor 画面",
|
||||
"이 기기의 폴더": "本设备上的文件夹",
|
||||
"이 단축키를 등록할 수 없습니다 (다른 프로그램이 사용 중일 수 있음)": "无法注册此快捷键(可能已被其他程序使用)",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import '../services/platform_services.dart';
|
|||
import '../services/system_usage.dart';
|
||||
import 'settings.dart';
|
||||
import 'cleanup.dart' show removeUpdateLeftovers;
|
||||
import 'master_lock.dart' show MasterLock;
|
||||
import '../l10n/tr.dart';
|
||||
import 'controller/ai_options.dart';
|
||||
import 'controller/ai_subtitle_runner.dart';
|
||||
|
|
@ -91,6 +92,8 @@ class AppController extends ChangeNotifier {
|
|||
final store = settingsStore;
|
||||
if (store == null) return true;
|
||||
final ok = await store.retrySecrets(settings);
|
||||
// M0: 저장소가 다시 읽히면 마스터 잠금도 다시 읽는다 (알 수 없음 → 평소대로)
|
||||
if (ok) await MasterLock.instance?.reload();
|
||||
_applySettings();
|
||||
notifyListeners();
|
||||
return ok;
|
||||
|
|
|
|||
|
|
@ -161,6 +161,13 @@ class PasswordHash {
|
|||
/// 마스터 입력 창에 넣은 값이 무엇이었는지
|
||||
enum MasterInput { master, superPassword, wrong, wait }
|
||||
|
||||
/// M0: 안전 저장소를 읽지 못해 마스터 · 최상 비밀번호 상태를 알 수 없을 때 바꾸려 함 (진짜 값을 덮어쓰지 않게 막는다)
|
||||
class MasterStoreUnreadable implements Exception {
|
||||
const MasterStoreUnreadable();
|
||||
@override
|
||||
String toString() => 'MasterStoreUnreadable';
|
||||
}
|
||||
|
||||
/// 124: 비밀번호 세 가지 중 ① 최상 (모두 초기화하는 열쇠) · ② 마스터 (저장된 비밀번호 · 설정 · 비밀번호가 필요한 기능을 쓸 때).
|
||||
/// 둘 다 사용자가 정하고, 솔트를 넣은 느린 해시로 안전 저장소에만 둔다. ③ 실제 비밀번호는 [SettingsStore] 가 다룬다.
|
||||
///
|
||||
|
|
@ -192,6 +199,10 @@ class MasterLock extends ChangeNotifier {
|
|||
DateTime? waitUntil;
|
||||
|
||||
bool get hasMaster => _master != null;
|
||||
|
||||
/// M0 (보안): 안전 저장소를 읽지 못했다 - 마스터가 있는지 알 수 없다. 그동안 [ensure] 는 문을 열지 않고 (한 번 다시 읽어 본다),
|
||||
/// 마스터 · 최상 · 묻는 때를 바꾸지 않는다 ([MasterStoreUnreadable]). 예전에는 빈 것으로 보아 마스터가 없는 것처럼 문이 열렸다
|
||||
bool storeUnreadable = false;
|
||||
bool get hasSuper => _super != null;
|
||||
|
||||
/// 'never' 물어보지 않기 · 'startup' 처음 시작 시 · 'onUse' 비밀번호가 저장된 기능을 쓸 때.
|
||||
|
|
@ -204,13 +215,23 @@ class MasterLock extends ChangeNotifier {
|
|||
|
||||
Future<void> setAsk(String value) async {
|
||||
if (!askValues.contains(value)) return;
|
||||
_writable();
|
||||
_askStored = value;
|
||||
await secrets.write(askKey, value);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 안전 저장소에서 읽는다. 읽지 못하면 [storeUnreadable] (예외를 내지 않음 - 앱은 켜지고 문은 닫힌다)
|
||||
Future<void> load() async {
|
||||
final all = await secrets.readAll();
|
||||
final Map<String, String> all;
|
||||
try {
|
||||
all = await secrets.readAll();
|
||||
} catch (_) {
|
||||
storeUnreadable = true;
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
storeUnreadable = false;
|
||||
_master = all[masterKey];
|
||||
_super = all[superKey];
|
||||
_askStored = all[askKey];
|
||||
|
|
@ -221,6 +242,16 @@ class MasterLock extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 다시 읽는다 (환경 설정 [다시 읽기] · 비밀번호 다시 저장이 된 뒤). 읽었으면 true
|
||||
Future<bool> reload() async {
|
||||
await load();
|
||||
return !storeUnreadable;
|
||||
}
|
||||
|
||||
void _writable() {
|
||||
if (storeUnreadable) throw const MasterStoreUnreadable();
|
||||
}
|
||||
|
||||
/// 지금 기다려야 하는 시간 (없으면 null)
|
||||
Duration? get waiting {
|
||||
final w = waitUntil;
|
||||
|
|
@ -266,6 +297,7 @@ class MasterLock extends ChangeNotifier {
|
|||
|
||||
/// 마스터를 정한다 · 바꾼다. 최상과 같으면 false (정하지 않음)
|
||||
Future<bool> setMaster(String password) async {
|
||||
_writable();
|
||||
final s = _super;
|
||||
if (s != null && await PasswordHash.verifyAsync(password, s)) return false;
|
||||
_master = await PasswordHash.makeAsync(password);
|
||||
|
|
@ -277,6 +309,7 @@ class MasterLock extends ChangeNotifier {
|
|||
|
||||
/// 최상을 정한다 · 바꾼다. 마스터와 같으면 false
|
||||
Future<bool> setSuper(String password) async {
|
||||
_writable();
|
||||
final m = _master;
|
||||
if (m != null && await PasswordHash.verifyAsync(password, m)) return false;
|
||||
_super = await PasswordHash.makeAsync(password);
|
||||
|
|
@ -289,6 +322,7 @@ class MasterLock extends ChangeNotifier {
|
|||
static const vaultCopyKey = 'vault.masterCopy';
|
||||
|
||||
Future<void> clearMaster() async {
|
||||
_writable();
|
||||
_master = null;
|
||||
_askStored = null;
|
||||
await secrets.delete(masterKey);
|
||||
|
|
@ -301,6 +335,7 @@ class MasterLock extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<void> clearSuper() async {
|
||||
_writable();
|
||||
_super = null;
|
||||
await secrets.delete(superKey);
|
||||
notifyListeners();
|
||||
|
|
@ -321,7 +356,9 @@ class MasterLock extends ChangeNotifier {
|
|||
/// 비밀번호가 저장된 기능을 쓰기 전에: 필요하면 마스터를 묻는다. 써도 되면 true.
|
||||
/// - 시작할 때 · 설정 화면 · 배경 작업이 동시에 불러도 창은 하나 (128).
|
||||
/// - 한 번 취소하면 이번 실행 동안 자동 · 배경 호출은 묻지 않고 false. [force]: 사용자가 직접 누른 곳 (다시 묻는다)
|
||||
/// - M0: 안전 저장소를 읽지 못했으면 한 번 다시 읽어 보고, 그래도 못 읽으면 열지 않는다 (false)
|
||||
Future<bool> ensure({bool force = false, String? reason}) {
|
||||
if (storeUnreadable) return reload().then((ok) => ok ? ensure(force: force, reason: reason) : false);
|
||||
if (!needsPrompt()) return Future.value(true);
|
||||
final pending = _pending;
|
||||
if (pending != null) return pending;
|
||||
|
|
|
|||
|
|
@ -142,8 +142,8 @@ class SettingsStore {
|
|||
stored = await secrets.readAll();
|
||||
_secretsOk = true;
|
||||
} catch (_) {
|
||||
// TODO(M0): 비밀 저장소를 못 읽으면 빈 것으로 보고 이어 간다 - 마스터가 없는 것으로 보여 문이 열린다 (보안).
|
||||
// 설정 나누기 (A) 뒤 따로 고친다 (관리자 10/22). 나누기 동안 동작은 그대로
|
||||
// 비밀 저장소를 못 읽으면 빈 것으로 보고 이어 간다 (앱은 켜지고 위 알림 [다시 시도]). 마스터 잠금은 따로 '알 수 없음' 으로 두어
|
||||
// 문을 열지 않는다 (M0 - MasterLock.storeUnreadable)
|
||||
stored = {};
|
||||
_secretsOk = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||
import '../app/app_controller.dart';
|
||||
import '../app/master_lock.dart';
|
||||
import '../l10n/tr.dart';
|
||||
import 'theme.dart';
|
||||
import 'confirm.dart';
|
||||
import 'master_prompt.dart';
|
||||
import 'setting_tile.dart';
|
||||
|
|
@ -24,6 +25,19 @@ class SecuritySettings extends StatelessWidget {
|
|||
// 바꾸기 · 끄기 전에 지금 비밀번호 (마스터 또는 최상)
|
||||
Future<bool> current() => askMaster(context, c, lock,
|
||||
forChange: true, reason: tr('바꾸거나 끄려면 지금 마스터 비밀번호 (또는 최상 비밀번호) 를 넣으세요.'));
|
||||
// M0: 안전 저장소를 읽지 못함 - 마스터가 있는지 알 수 없어 바꾸지 못하게 (진짜 값을 덮어쓰지 않게) 하고 까닭 · [다시 읽기]
|
||||
if (lock.storeUnreadable) {
|
||||
return SettingTile(
|
||||
leading: Icon(Icons.lock_clock_outlined, color: JjColors.warning),
|
||||
title: Text(tr('마스터 비밀번호 - 안전 저장소를 읽지 못함')),
|
||||
subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(tr('이 기기의 안전 저장소를 읽지 못해 마스터 비밀번호가 있는지 알 수 없습니다. '
|
||||
'그동안 저장된 비밀번호를 쓰는 기능은 열지 않고, 마스터 · 최상 비밀번호는 바꿀 수 없습니다.')),
|
||||
const SizedBox(height: 6),
|
||||
FilledButton.tonal(onPressed: () => lock.reload(), child: Text(tr('다시 읽기'))),
|
||||
]),
|
||||
);
|
||||
}
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||
SettingTile(
|
||||
leading: const Icon(Icons.lock_outline),
|
||||
|
|
|
|||
|
|
@ -314,6 +314,11 @@ Future<void> vaultToggleMasterCopy(BuildContext context, AppController c, VaultU
|
|||
final lock = MasterLock.instance;
|
||||
if (lock == null) return;
|
||||
if (on) {
|
||||
// M0: 안전 저장소를 읽지 못하면 마스터가 있는지 알 수 없다 - 정하라고 하지 않고 까닭을
|
||||
if (lock.storeUnreadable) {
|
||||
s.snack(tr('이 기기의 안전 저장소를 읽지 못해 지금은 켤 수 없습니다 (환경 설정 > 보안 [다시 읽기])'));
|
||||
return;
|
||||
}
|
||||
if (!lock.hasMaster) {
|
||||
s.snack(tr('먼저 환경 설정 > 보안 에서 마스터 비밀번호를 정하세요'));
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:jj_mkvmaker/app/master_lock.dart';
|
||||
import 'package:jj_mkvmaker/services/secret_store.dart';
|
||||
|
||||
/// 읽기가 될 때와 안 될 때를 바꿀 수 있는 안전 저장소 (기기 저장소가 잠깐 잠긴 때처럼)
|
||||
class _FlakyStore implements SecretStore {
|
||||
final Map<String, String> values = {};
|
||||
bool readable = true;
|
||||
@override
|
||||
String? lostCopy;
|
||||
@override
|
||||
Future<Map<String, String>> readAll() async {
|
||||
if (!readable) throw StateError('안전 저장소를 읽을 수 없음');
|
||||
return Map.of(values);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> write(String key, String value) async => values[key] = value;
|
||||
@override
|
||||
Future<void> delete(String key) async => values.remove(key);
|
||||
}
|
||||
|
||||
/// M0 약속: 안전 저장소를 읽지 못하면 마스터 잠금은 '알 수 없음' - 문을 열지 않고 (한 번 다시 읽어 봄), 마스터 · 최상 · 묻는 때를 바꾸지 않는다.
|
||||
/// 다시 읽히면 평소대로 (마스터가 있으면 묻는다)
|
||||
void main() {
|
||||
late _FlakyStore store;
|
||||
late int asked;
|
||||
setUp(() async {
|
||||
store = _FlakyStore();
|
||||
// 마스터가 정해진 기기
|
||||
final setup = MasterLock(store);
|
||||
await setup.load();
|
||||
await setup.setMaster('right-pass');
|
||||
asked = 0;
|
||||
MasterLock.prompt = (_) async {
|
||||
asked++;
|
||||
return true;
|
||||
};
|
||||
});
|
||||
tearDown(() => MasterLock.prompt = null);
|
||||
|
||||
test('읽지 못하면 문을 열지 않는다 (예전: 마스터가 없는 것으로 보여 열렸다)', () async {
|
||||
store.readable = false;
|
||||
final lock = MasterLock(store);
|
||||
await lock.load(); // 예외를 내지 않는다 - 앱은 켜진다
|
||||
expect([lock.storeUnreadable, lock.hasMaster], [true, false]);
|
||||
expect(await lock.ensure(), isFalse);
|
||||
expect(await lock.ensure(force: true), isFalse, reason: '사용자가 직접 눌러도 열지 않는다');
|
||||
expect(asked, 0, reason: '알 수 없는 마스터를 묻지도 않는다');
|
||||
});
|
||||
|
||||
test('알 수 없는 동안 마스터 · 최상 · 묻는 때를 바꾸지 않는다 (진짜 마스터를 덮어쓰지 않게)', () async {
|
||||
final before = Map.of(store.values);
|
||||
store.readable = false;
|
||||
final lock = MasterLock(store);
|
||||
await lock.load();
|
||||
await expectLater(lock.setMaster('new-pass'), throwsA(isA<MasterStoreUnreadable>()));
|
||||
await expectLater(lock.setSuper('s'), throwsA(isA<MasterStoreUnreadable>()));
|
||||
await expectLater(lock.clearMaster(), throwsA(isA<MasterStoreUnreadable>()));
|
||||
await expectLater(lock.setAsk('never'), throwsA(isA<MasterStoreUnreadable>()));
|
||||
expect(store.values, before);
|
||||
});
|
||||
|
||||
test('다시 읽히면 평소대로: 문을 열 때 한 번 다시 읽고, 마스터가 있으면 묻는다', () async {
|
||||
store.readable = false;
|
||||
final lock = MasterLock(store);
|
||||
await lock.load();
|
||||
store.readable = true;
|
||||
expect(await lock.ensure(), isTrue);
|
||||
expect([lock.storeUnreadable, lock.hasMaster, asked], [false, true, 1]);
|
||||
});
|
||||
|
||||
test('reload: 읽혔는지 돌려준다', () async {
|
||||
store.readable = false;
|
||||
final lock = MasterLock(store);
|
||||
await lock.load();
|
||||
expect(await lock.reload(), isFalse);
|
||||
store.readable = true;
|
||||
expect(await lock.reload(), isTrue);
|
||||
expect(lock.hasMaster, isTrue);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue