// SPDX-FileCopyrightText: 2026 FocusFlow contributors // SPDX-License-Identifier: AGPL-3.0-only /// Tests FocusFlow runtime config and file logging behavior. library; import 'dart:convert'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:focus_flow_flutter/app/runtime/focus_flow_file_logger.dart'; import 'package:focus_flow_flutter/app/runtime/focus_flow_runtime_config.dart'; import 'package:scheduler_core/scheduler_core.dart' as scheduler_core; /// Runs runtime config tests. void main() { tearDown(scheduler_core.logger.disable); group('FocusFlowRuntimeConfig', () { test('missing config file leaves optional settings unset', () async { final directory = await Directory.systemTemp.createTemp( 'focus-flow-config-test-', ); addTearDown(() => directory.delete(recursive: true)); final config = await FocusFlowRuntimeConfig.load( path: '${directory.path}${Platform.pathSeparator}missing.json', ); expect(config.logLevel, isNull); expect(config.logFileLocation, isNull); expect(config.enablesFileLogging, isFalse); }); test('loads supported settings from a config file', () async { final directory = await Directory.systemTemp.createTemp( 'focus-flow-config-file-test-', ); addTearDown(() => directory.delete(recursive: true)); final configFile = File( '${directory.path}${Platform.pathSeparator}config.json', ); await configFile.writeAsString( jsonEncode({'LogLevel': 'finest', 'LogfileLocation': directory.path}), ); final config = await FocusFlowRuntimeConfig.load(path: configFile.path); expect(config.logLevel, FocusFlowLogLevel.finest); expect(config.logFileLocation, directory.path); expect(config.enablesFileLogging, isTrue); }); test('parses every supported log level', () { expect(FocusFlowLogLevel.tryParse('finest'), FocusFlowLogLevel.finest); expect(FocusFlowLogLevel.tryParse('finer'), FocusFlowLogLevel.finer); expect(FocusFlowLogLevel.tryParse('fine'), FocusFlowLogLevel.fine); expect(FocusFlowLogLevel.tryParse('debug'), FocusFlowLogLevel.debug); expect(FocusFlowLogLevel.tryParse('info'), FocusFlowLogLevel.info); expect(FocusFlowLogLevel.tryParse('warn'), FocusFlowLogLevel.warn); expect(FocusFlowLogLevel.tryParse('error'), FocusFlowLogLevel.error); }); test('loads supported logging settings from JSON', () { final config = FocusFlowRuntimeConfig.fromJson({ 'LogLevel': 'debug', 'LogfileLocation': '/tmp/focus-flow-logs', }); expect(config.logLevel, FocusFlowLogLevel.debug); expect(config.logFileLocation, '/tmp/focus-flow-logs'); expect(config.enablesFileLogging, isTrue); }); test('ignores missing and unsupported optional settings', () { final config = FocusFlowRuntimeConfig.fromJson({ 'LogLevel': 'verbose', 'LogfileLocation': ' ', }); expect(config.logLevel, isNull); expect(config.logFileLocation, isNull); expect(config.enablesFileLogging, isFalse); }); }); group('FocusFlowFileLogger', () { test('does nothing unless both logging settings are set', () async { final logger = await FocusFlowFileLogger.open( const FocusFlowRuntimeConfig(logLevel: FocusFlowLogLevel.debug), ); expect(logger.isEnabled, isFalse); await logger.info('ignored'); }); test('writes messages at or above the configured level', () async { final directory = await Directory.systemTemp.createTemp( 'focus-flow-log-test-', ); addTearDown(() => directory.delete(recursive: true)); final logger = await FocusFlowFileLogger.open( FocusFlowRuntimeConfig( logLevel: FocusFlowLogLevel.warn, logFileLocation: directory.path, ), now: () => DateTime.utc(2026, 7, 3, 12), ); await logger.info('not written'); await logger.warn('written warning'); await logger.error('written error', error: 'boom'); final logFile = File( '${directory.path}${Platform.pathSeparator}focus_flow.log', ); final contents = await logFile.readAsString(); expect(contents, isNot(contains('not written'))); expect(contents, contains('2026-07-03T12:00:00.000Z WARN')); expect(contents, contains('written warning')); expect(contents, contains('ERROR written error | error=boom')); }); test('skips lazy message work below the configured level', () async { final directory = await Directory.systemTemp.createTemp( 'focus-flow-lazy-skip-test-', ); addTearDown(() => directory.delete(recursive: true)); final logger = await FocusFlowFileLogger.open( FocusFlowRuntimeConfig( logLevel: FocusFlowLogLevel.info, logFileLocation: directory.path, ), ); var evaluated = false; await logger.debug(() { evaluated = true; return 'expensive debug state'; }); expect(evaluated, isFalse); expect( File( '${directory.path}${Platform.pathSeparator}focus_flow.log', ).existsSync(), isFalse, ); }); test('evaluates lazy messages once the level is enabled', () async { final directory = await Directory.systemTemp.createTemp( 'focus-flow-lazy-write-test-', ); addTearDown(() => directory.delete(recursive: true)); final logger = await FocusFlowFileLogger.open( FocusFlowRuntimeConfig( logLevel: FocusFlowLogLevel.debug, logFileLocation: directory.path, ), now: () => DateTime.utc(2026, 7, 3, 12), ); var evaluations = 0; await logger.debug(() { evaluations += 1; return 'computed debug state'; }); expect(evaluations, 1); final contents = await File( '${directory.path}${Platform.pathSeparator}focus_flow.log', ).readAsString(); expect(contents, contains('DEBUG computed debug state')); }); test( 'finest captures caller information for every written level', () async { final directory = await Directory.systemTemp.createTemp( 'focus-flow-finest-caller-test-', ); addTearDown(() => directory.delete(recursive: true)); final logger = await FocusFlowFileLogger.open( FocusFlowRuntimeConfig( logLevel: FocusFlowLogLevel.finest, logFileLocation: directory.path, ), now: () => DateTime.utc(2026, 7, 3, 12), ); await logger.info('high level message'); final contents = await File( '${directory.path}${Platform.pathSeparator}focus_flow.log', ).readAsString(); expect(contents, contains('INFO caller=')); expect(contents, contains('runtime_config_test.dart')); expect(contents, contains('high level message')); }, ); test('fine captures caller information for warnings and errors', () async { final directory = await Directory.systemTemp.createTemp( 'focus-flow-fine-caller-test-', ); addTearDown(() => directory.delete(recursive: true)); final logger = await FocusFlowFileLogger.open( FocusFlowRuntimeConfig( logLevel: FocusFlowLogLevel.fine, logFileLocation: directory.path, ), now: () => DateTime.utc(2026, 7, 3, 12), ); await logger.warn('handled warning'); final contents = await File( '${directory.path}${Platform.pathSeparator}focus_flow.log', ).readAsString(); expect(contents, contains('WARN caller=')); expect(contents, contains('runtime_config_test.dart')); expect(contents, contains('handled warning')); }); test('info does not capture caller information for warnings', () async { final directory = await Directory.systemTemp.createTemp( 'focus-flow-info-caller-test-', ); addTearDown(() => directory.delete(recursive: true)); final logger = await FocusFlowFileLogger.open( FocusFlowRuntimeConfig( logLevel: FocusFlowLogLevel.info, logFileLocation: directory.path, ), now: () => DateTime.utc(2026, 7, 3, 12), ); await logger.warn('minimal warning'); final contents = await File( '${directory.path}${Platform.pathSeparator}focus_flow.log', ).readAsString(); expect(contents, contains('WARN minimal warning')); expect(contents, isNot(contains('caller='))); expect(contents, isNot(contains('runtime_config_test.dart'))); }); }); }