Files
pj_supermarket_cloud/test/file_access_mode_test.dart
T
2026-10-29 15:46:54 +08:00

165 lines
5.4 KiB
Dart

import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:pj_supermarket_cloud/common/utils/app/app_network_tool.dart';
import 'package:pj_supermarket_cloud/common/utils/app/file_access.dart';
import 'package:pj_supermarket_cloud/common/values/constants.dart';
void main() {
final testKey = base64Encode(List.generate(32, (index) => index));
FileAccessMode select({
String mode = 'auto',
bool debug = true,
bool configured = false,
String appId = 'flutter',
String kid = 'v1',
String key = '',
}) =>
resolveFileAccessMode(
mode: mode,
debugBuild: debug,
hasConfiguration: configured,
appId: appId,
kid: kid,
key: key,
);
test('an ordinary Debug launch without configuration simulates old clients',
() {
expect(select(), FileAccessMode.legacy);
});
test('valid configuration selects signing in Debug and production builds',
() {
for (final debug in [true, false]) {
for (final mode in ['auto', 'signed']) {
expect(
select(mode: mode, debug: debug, configured: true, key: testKey),
FileAccessMode.signed,
);
}
}
});
test('partial or invalid configuration never silently disables signing', () {
for (final key in [
'',
'not-base64!',
base64Encode([1, 2, 3])
]) {
expect(() => select(configured: true, key: key), throwsStateError);
}
expect(
() => select(configured: true, appId: 'admin', key: testKey),
throwsStateError,
);
expect(
() => select(configured: true, kid: 'invalid key id', key: testKey),
throwsStateError,
);
});
test('production and explicit signed launches reject missing credentials',
() {
expect(() => select(debug: false), throwsStateError);
expect(() => select(mode: 'signed'), throwsStateError);
});
test(
'explicit legacy testing stays unsigned even when credentials are present',
() {
expect(
select(mode: 'legacy', configured: true, key: testKey),
FileAccessMode.legacy,
);
expect(() => select(mode: 'legacy', debug: false), throwsStateError);
expect(() => select(mode: 'typo'), throwsStateError);
});
test(
'legacy image requests omit all signature fields and preserve file names',
() {
final client = FileAccessClient(
baseUrl: 'https://files.example/api',
appId: 'flutter',
kid: 'v1',
key: '',
signingEnabled: false,
);
const source = '/admin/sys-file/oss/file?appId=flutter&kid=v1&ts=1&sign=old'
'&width=20&fileName=literal%252Fname.png';
final url = Uri.parse(client.url(source));
expect(url.path, '/api/admin/sys-file/oss/file');
expect(
url.queryParameters, {'width': '20', 'fileName': 'literal%2Fname.png'});
expect(client.url(source, force: true), url.toString());
expect(client.url('photo.jpg'),
'https://files.example/api/admin/sys-file/oss/file?fileName=photo.jpg');
const external = 'https://other.example/image.png?sign=third-party';
expect(client.url(external), external);
expect(client.url('assets/logo.png'), 'assets/logo.png');
});
test('legacy uploads carry no app signature headers and need no key', () {
final client = FileAccessClient(
baseUrl: 'https://files.example/api',
appId: 'flutter',
kid: 'v1',
key: '',
signingEnabled: false,
);
for (final target in ['upload', 'inner-upload', 'qn/upload']) {
expect(client.uploadHeaders('/admin/sys-file/$target'), isEmpty);
}
});
test('the compiled launch configuration controls image and upload signing',
() {
final expectedSigning = Constants.fileAccessMode != 'legacy' &&
Constants.hasFileAccessConfiguration;
expect(FileAccess.signingEnabled, expectedSigning);
final uri = Uri.parse(FileAccess.client.url('photo.jpg'));
expect(uri.queryParameters.containsKey('sign'), expectedSigning);
expect(
FileAccess.client.uploadHeaders('/admin/sys-file/upload').isNotEmpty,
expectedSigning,
);
// Old clients can still consume package links signed by the server.
const package = 'https://files.example/admin/sys-file/oss/file'
'?appId=package&kid=v1&ts=1&sign=server&fileName=app.apk';
expect(FileAccess.packageUrl(package), package);
});
test(
'legacy image downloads send bare requests and do not retry a rejected signature',
() async {
final requests = <http.Request>[];
final client = MockClient((request) async {
requests.add(request);
return http.Response('', 403);
});
addTearDown(client.close);
expect(await AppNetworkTool.downloadImage('photo.jpg', client: client),
isNull);
expect(requests, hasLength(1));
expect(requests.single.url.queryParameters, {'fileName': 'photo.jpg'});
}, skip: FileAccess.signingEnabled);
testWidgets(
'unsigned startup avoids platform initialization and renewal timers',
(tester) async {
FileAccess.initialize();
FileAccess.initialize();
final before = FileAccess.revision.value;
FileAccess.resume();
await FileAccess.refreshClock(force: true);
expect(await FileAccess.retryImage('photo.jpg'), isFalse);
await tester.pump(const Duration(minutes: 2));
expect(FileAccess.revision.value, before);
FileAccess.pause();
}, skip: FileAccess.signingEnabled);
}