mirror of
https://github.com/immich-app/immich.git
synced 2026-06-05 06:15:24 -04:00
b3d49045de
* Feat - Heatmap * Implemented Comments to prettify and code cleanup * fixing code to pass cases. * fixing errors for OpenAPI Clients * Improving the code. * Fix code * Rerun generated client check * Rerun generated client * feat: command for user pages (#28554) * fix(web): timeline stuttering with many assets in 1 day (#28509) * fix(web): timeline stuttering with many assets in 1 day * cache isInOrNearViewport per day * skip inOrNearViewport check on first run * chore(ml): allow insightface 1.x (#28595) * chore(ml): allow insightface 1.x The new insightface 1.0 release appears to have no breaking code changes nor relevant license changes ([before](https://github.com/deepinsight/insightface/blob/2a78baec428354883e0cda39c54b555a5ed8358a/README.md), [after](https://github.com/deepinsight/insightface/blob/70f3269ea628d0658c5723976944c9de414e96f8/README.md), c.f. https://github.com/immich-app/immich/blob/fd7ddfef54cdf2b6256c4fc08bc5ff3f86176775/machine-learning/README.md), and it works on my machine. * Update uv.lock * please excuse my incompetence * Triggering the actions. * bad merge * Fix code * Code clear * Resolve conflict * Resolve conflict * Resolve conflict * Resolve errors * Resolve errors * Resolve errors more * chore: clean up --------- Co-authored-by: Alex <alex.tran1502@gmail.com> Co-authored-by: Ben Beckford <ben@benjaminbeckford.com> Co-authored-by: Aaron Liu <aaronliu0130@gmail.com> Co-authored-by: Jason Rasmussen <jason@rasm.me>
118 lines
3.5 KiB
Dart
118 lines
3.5 KiB
Dart
// Intentionally NOT named `*_test.dart`: that suffix makes `flutter test`
|
|
// auto-discover it, which would run it on every mobile PR. This check is only
|
|
// relevant when the OpenAPI spec changes, so the `Check OpenAPI` workflow runs
|
|
// it by explicit path with the spec locations in the environment.
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:immich_mobile/utils/openapi_patching.dart';
|
|
|
|
void main() {
|
|
test('every newly-required response field has a backward-compat patch', () {
|
|
final basePath = Platform.environment['OPENAPI_BASE_SPEC'];
|
|
final revisionPath = Platform.environment['OPENAPI_REVISION_SPEC'];
|
|
if (basePath == null || revisionPath == null) {
|
|
markTestSkipped('set OPENAPI_BASE_SPEC and OPENAPI_REVISION_SPEC to run');
|
|
return;
|
|
}
|
|
|
|
final baseRequired = _requiredBySchema(_loadSpec(basePath));
|
|
final revisionSpec = _loadSpec(revisionPath);
|
|
final revisionRequired = _requiredBySchema(revisionSpec);
|
|
final deserialized = _deserializedSchemas(revisionSpec);
|
|
final patched = openApiPatches.map(
|
|
(type, fields) => MapEntry(type, fields.keys.toSet()),
|
|
);
|
|
|
|
final missing = <String>[];
|
|
for (final entry in revisionRequired.entries) {
|
|
if (!deserialized.contains(entry.key)) {
|
|
continue;
|
|
}
|
|
|
|
// Skip new DTOs
|
|
if (!baseRequired.containsKey(entry.key)) {
|
|
continue;
|
|
}
|
|
|
|
final have = patched[entry.key] ?? const <String>{};
|
|
final newlyRequired = entry.value.difference(
|
|
baseRequired[entry.key] ?? const <String>{},
|
|
);
|
|
for (final field in newlyRequired) {
|
|
if (!have.contains(field)) {
|
|
missing.add('${entry.key}.$field');
|
|
}
|
|
}
|
|
}
|
|
missing.sort();
|
|
|
|
expect(
|
|
missing,
|
|
isEmpty,
|
|
reason:
|
|
'Detected a breaking change: $missing\n'
|
|
'Either add a default to openApiPatches in lib/utils/openapi_patching.dart, or make it optional',
|
|
);
|
|
});
|
|
}
|
|
|
|
Map<String, dynamic> _loadSpec(String path) =>
|
|
jsonDecode(File(path).readAsStringSync()) as Map<String, dynamic>;
|
|
|
|
Map<String, dynamic> _schemas(Map<String, dynamic> spec) =>
|
|
((spec['components'] as Map?)?['schemas'] as Map?)
|
|
?.cast<String, dynamic>() ??
|
|
const {};
|
|
|
|
Map<String, Set<String>> _requiredBySchema(Map<String, dynamic> spec) {
|
|
final result = <String, Set<String>>{};
|
|
_schemas(spec).forEach((name, schema) {
|
|
final required = (schema as Map)['required'] as List? ?? const [];
|
|
result[name] = required.cast<String>().toSet();
|
|
});
|
|
return result;
|
|
}
|
|
|
|
Iterable<String> _refsIn(Object? node) sync* {
|
|
if (node is Map) {
|
|
if (node[r'$ref'] case final String ref) {
|
|
yield ref.split('/').last;
|
|
}
|
|
for (final value in node.values) {
|
|
yield* _refsIn(value);
|
|
}
|
|
} else if (node is List) {
|
|
for (final value in node) {
|
|
yield* _refsIn(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
Set<String> _deserializedSchemas(Map<String, dynamic> spec) {
|
|
final schemas = _schemas(spec);
|
|
final reachable = <String>{};
|
|
|
|
final queue = <String>[];
|
|
for (final path in (spec['paths'] as Map?)?.values ?? const []) {
|
|
if (path is! Map) {
|
|
continue;
|
|
}
|
|
for (final operation in path.values) {
|
|
if (operation is Map) {
|
|
queue.addAll(_refsIn(operation['responses']));
|
|
}
|
|
}
|
|
}
|
|
while (queue.isNotEmpty) {
|
|
final name = queue.removeLast();
|
|
if (!schemas.containsKey(name) || !reachable.add(name)) {
|
|
continue;
|
|
}
|
|
queue.addAll(_refsIn(schemas[name]));
|
|
}
|
|
return reachable;
|
|
}
|