@xiangyang9999/folib-server-scan-export-sync 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,73 @@
1
- # Temporary Holding Version
1
+ # @xiangyang9999/folib-server-scan-export-sync
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 版本 `1.0.1`:扫描报告导出测试修复的离线手工同步增量包。
4
+
5
+ - 源提交:`454e63ce953114b039fe3de06185f5a9a4d1c812`。
6
+ - 相对父提交:`d272e5684448e626126ad070cf9a52a3cc9f1eef`。
7
+ - 使用前提:已同步扫描导出功能提交 `a28ada525f48cf8b6ea3024d4f3eda83cc6d358a`,或旧资料包 `1.0.0`。
8
+ - 共 7 个文件:新增 1 个、修改 6 个;沿用旧包范围,排除 `tools/scan-export/`。
9
+ - 这 7 个文件在 `a28ada52` 与修复提交的父提交之间没有其他改动,可直接接在旧包后同步。
10
+ - 本包从 Git 提交提取代码,不包含当前工作区未提交的修改或中间的前端依赖提交。
11
+
12
+ ## 修复内容
13
+
14
+ 恢复固定的 `org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker`,移除 web POM 中本功能新增的 MockMaker 属性和测试资源过滤。两个新增导出测试改用真实请求上下文、RepositoryPath 和普通 mock。storage 模块新增 9 项 Repository 查询测试,并修复原漏洞筛选测试的 mock 链及断言。
15
+
16
+ `ArtifactRepository.java` 的生产实现、storage POM、前端及数据库均没有本次增量变更。旧包中的扫描导出业务代码继续保留;web POM 的变更是恢复原有配置,不添加新的打包或覆盖率参数。
17
+
18
+ ## 包内资料
19
+
20
+ | 路径 | 内容 |
21
+ | --- | --- |
22
+ | `payload/内网手工同步修改说明.docx` | 同步前提、逐文件说明、新增文件完整代码、修改文件精确差异及验证命令 |
23
+ | `payload/涉及文件_按原路径.zip` | 7 个提交版本文件,保留原路径;附文件清单和含 SHA-256 的 `manifest.json` |
24
+ | `payload/完整变更补丁_供核对.patch` | 本次 7 个文件的精确增量补丁;排除 tools 目录 |
25
+
26
+ ZIP 中既有文件提供的是完整提交版本,主要用于核对;手工同步时按 Word 或补丁逐段修改,避免覆盖目标环境自身的其他改动。
27
+
28
+ ## 离线安装与提取
29
+
30
+ 将 `.tgz` 复制到目标机器后执行:
31
+
32
+ ```bash
33
+ npm install ./xiangyang9999-folib-server-scan-export-sync-1.0.1.tgz
34
+ npx --no-install folib-scan-export-sync list
35
+ npx --no-install folib-scan-export-sync extract ./folib-sync-454e63ce9
36
+ ```
37
+
38
+ 提取命令只复制三个资料文件,不修改项目代码。默认拒绝覆盖已有资料;需要替换时显式添加 `--force`。
39
+
40
+ ```bash
41
+ npx --no-install folib-scan-export-sync extract ./folib-sync-454e63ce9 --force
42
+ ```
43
+
44
+ ## 手工同步顺序
45
+
46
+ 1. 确认旧包 `1.0.0` 对应的导出功能已同步。
47
+ 2. 恢复 `folib-web-core/pom.xml` 中原有配置及固定的 MockMaker 资源。
48
+ 3. 同步两个 web 导出测试的差异。
49
+ 4. 新增 storage 的 `ArtifactRepositoryScanExportTest.java`,修复 `ArtifactRepositoryTest.java` 中的漏洞筛选用例。
50
+ 5. 更新 `SCAN_EXPORT.md`,执行其中的定向测试命令,按现有 CI 流程收集 storage 覆盖率。
51
+
52
+ ## 已完成的本地验证
53
+
54
+ 2026-10-10:web 定向回归 141 项通过,其中包含 10 类导出测试的 77 项;storage 新增 9 项与修复的 1 项测试全部通过。使用本地临时命令行 JaCoCo agent,`ArtifactRepository.java` 第 205–277 行的 49 个可执行行全部命中,分支覆盖 74/81。没有修改 storage 的 Surefire 配置。
55
+
56
+ 上述结果来自本地验证,Jenkins 的实际覆盖率仍需由现有 CI 执行及收集。定向 `package` 验证不等同于正式发行包验收。
57
+
58
+ ## 重新打包
59
+
60
+ 在 `npm-package` 目录中执行:
61
+
62
+ ```bash
63
+ npm pack --dry-run --ignore-scripts
64
+ npm pack --ignore-scripts
65
+ ```
66
+
67
+ 如需上传到内网 npm 仓库,在该目录执行并替换仓库地址:
68
+
69
+ ```bash
70
+ npm publish --registry=https://npm.example.internal/
71
+ ```
72
+
73
+ 本目录生成的是本地安装包,未执行发布。
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ const files = [
8
+ '内网手工同步修改说明.docx',
9
+ '涉及文件_按原路径.zip',
10
+ '完整变更补丁_供核对.patch',
11
+ ];
12
+
13
+ const sourceDir = path.resolve(__dirname, '..', 'payload');
14
+ const args = process.argv.slice(2);
15
+
16
+ function usage() {
17
+ process.stdout.write(
18
+ '用法:\n' +
19
+ ' folib-scan-export-sync list\n' +
20
+ ' folib-scan-export-sync extract <输出目录> [--force]\n' +
21
+ ' folib-scan-export-sync --help\n\n' +
22
+ 'extract 只复制手工同步资料,默认拒绝覆盖已有文件。\n'
23
+ );
24
+ }
25
+
26
+ function main() {
27
+ const command = args[0] || '--help';
28
+ if (command === '--help' || command === '-h' || command === 'help') {
29
+ usage();
30
+ return;
31
+ }
32
+
33
+ for (const file of files) {
34
+ const src = path.join(sourceDir, file);
35
+ if (!fs.statSync(src).isFile()) {
36
+ throw new Error('缺少包内文件: ' + src);
37
+ }
38
+ }
39
+
40
+ if (command === 'list') {
41
+ for (const file of files) {
42
+ const bytes = fs.statSync(path.join(sourceDir, file)).size;
43
+ process.stdout.write(file + ' (' + bytes + ' bytes)\n');
44
+ }
45
+ return;
46
+ }
47
+
48
+ if (command !== 'extract' || !args[1] || args.some((a, i) => i >= 2 && a !== '--force')) {
49
+ usage();
50
+ process.exitCode = 2;
51
+ return;
52
+ }
53
+
54
+ const force = args.includes('--force');
55
+ const outDir = path.resolve(args[1]);
56
+ const targets = files.map((file) => ({ src: path.join(sourceDir, file), dst: path.join(outDir, file) }));
57
+
58
+ if (!force) {
59
+ const existing = targets.filter(({ dst }) => fs.existsSync(dst));
60
+ if (existing.length > 0) {
61
+ throw new Error('为避免覆盖,已停止。目标文件已存在: ' + existing.map(({ dst }) => dst).join(', ') + ';需要覆盖时加 --force');
62
+ }
63
+ }
64
+
65
+ fs.mkdirSync(outDir, { recursive: true });
66
+ for (const { src, dst } of targets) {
67
+ fs.copyFileSync(src, dst, force ? 0 : fs.constants.COPYFILE_EXCL);
68
+ process.stdout.write('已复制: ' + dst + '\n');
69
+ }
70
+ }
71
+
72
+ try {
73
+ main();
74
+ } catch (err) {
75
+ process.stderr.write('错误: ' + err.message + '\n');
76
+ process.exitCode = 1;
77
+ }
package/package.json CHANGED
@@ -1,6 +1,26 @@
1
1
  {
2
2
  "name": "@xiangyang9999/folib-server-scan-export-sync",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.1",
4
+ "description": "Offline incremental sync bundle for folib-server scan export Mockito compatibility and coverage fix commit 454e63ce9.",
5
+ "license": "UNLICENSED",
6
+ "bin": {
7
+ "folib-scan-export-sync": "./bin/folib-scan-export-sync.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "payload/",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=16"
16
+ },
17
+ "keywords": [
18
+ "folib-server",
19
+ "scan-export",
20
+ "offline-sync",
21
+ "manual-migration",
22
+ "internal-code"
23
+ ],
24
+ "sourceCommit": "454e63ce953114b039fe3de06185f5a9a4d1c812",
25
+ "syncBaseCommit": "a28ada525f48cf8b6ea3024d4f3eda83cc6d358a"
26
+ }
@@ -0,0 +1,561 @@
1
+ diff --git a/folib-storage/folib-storage-api/src/test/java/com/veadan/folib/repositories/ArtifactRepositoryScanExportTest.java b/folib-storage/folib-storage-api/src/test/java/com/veadan/folib/repositories/ArtifactRepositoryScanExportTest.java
2
+ new file mode 100644
3
+ index 0000000000000000000000000000000000000000..5bda7653fd51427cc22fc6499aa2a55faa7b6e96
4
+ --- /dev/null
5
+ +++ b/folib-storage/folib-storage-api/src/test/java/com/veadan/folib/repositories/ArtifactRepositoryScanExportTest.java
6
+ @@ -0,0 +1,207 @@
7
+ +package com.veadan.folib.repositories;
8
+ +
9
+ +import com.veadan.folib.configuration.Configuration;
10
+ +import com.veadan.folib.configuration.ConfigurationManager;
11
+ +import com.veadan.folib.db.schema.Properties;
12
+ +import com.veadan.folib.db.schema.Vertices;
13
+ +import com.veadan.folib.domain.Artifact;
14
+ +import com.veadan.folib.domain.ArtifactEntity;
15
+ +import com.veadan.folib.gremlin.adapters.ArtifactAdapter;
16
+ +import com.veadan.folib.gremlin.dsl.EntityTraversalUtils;
17
+ +import com.veadan.folib.gremlin.dsl.__;
18
+ +import com.veadan.folib.services.ConfigurationManagementService;
19
+ +import com.veadan.folib.storage.Storage;
20
+ +import com.veadan.folib.storage.repository.Repository;
21
+ +import org.apache.tinkerpop.gremlin.structure.T;
22
+ +import org.apache.tinkerpop.gremlin.structure.Vertex;
23
+ +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
24
+ +import org.junit.jupiter.api.AfterEach;
25
+ +import org.junit.jupiter.api.BeforeEach;
26
+ +import org.junit.jupiter.api.Test;
27
+ +import org.springframework.test.util.ReflectionTestUtils;
28
+ +
29
+ +import java.time.LocalDateTime;
30
+ +import java.util.ArrayList;
31
+ +import java.util.LinkedHashMap;
32
+ +import java.util.List;
33
+ +import java.util.Map;
34
+ +import java.util.concurrent.CancellationException;
35
+ +import java.util.stream.Collectors;
36
+ +
37
+ +import static org.junit.jupiter.api.Assertions.*;
38
+ +import static org.mockito.ArgumentMatchers.*;
39
+ +import static org.mockito.Mockito.*;
40
+ +
41
+ +/** Run the real export traversal in its owning module so module-level coverage includes it. */
42
+ +class ArtifactRepositoryScanExportTest {
43
+ + private TinkerGraph graph;
44
+ + private ArtifactRepository artifacts;
45
+ + private ConfigurationManager manager;
46
+ + private Configuration configuration;
47
+ + private Storage storage;
48
+ + private Map<String, Repository> repositories;
49
+ +
50
+ + @BeforeEach void setup() {
51
+ + graph = TinkerGraph.open();
52
+ + artifacts = new ArtifactRepository();
53
+ + manager = mock(ConfigurationManager.class);
54
+ + configuration = mock(Configuration.class);
55
+ + storage = mock(Storage.class);
56
+ + repositories = new LinkedHashMap<>();
57
+ + addRepository("r1", "hosted");
58
+ + addRepository("r2", "hosted");
59
+ + when(storage.getId()).thenReturn("s");
60
+ + doReturn(repositories).when(storage).getRepositories();
61
+ + when(configuration.getStorages()).thenReturn(Map.of("s", storage));
62
+ + when(manager.getRepository(eq("s"), anyString())).thenAnswer(c -> repositories.get(c.getArgument(1)));
63
+ + when(configuration.getRepository(eq("s"), anyString())).thenAnswer(c -> repositories.get(c.getArgument(1)));
64
+ + ConfigurationManagementService service = mock(ConfigurationManagementService.class);
65
+ + when(service.getConfiguration()).thenReturn(configuration);
66
+ + artifacts.configurationManager = manager;
67
+ + artifacts.configurationManagementService = service;
68
+ + ReflectionTestUtils.setField(artifacts, "graph", graph);
69
+ + // Substitute presentation only; predicates, ordering, cursors and limits execute on real vertices.
70
+ + ArtifactAdapter adapter = mock(ArtifactAdapter.class);
71
+ + when(adapter.searchFold(any())).thenAnswer(c -> __.<Vertex, Artifact>map(t -> {
72
+ + ArtifactEntity artifact = new ArtifactEntity();
73
+ + artifact.setUuid(t.get().value(Properties.UUID));
74
+ + return artifact;
75
+ + }));
76
+ + artifacts.artifactAdapter = adapter;
77
+ + }
78
+ +
79
+ + @AfterEach void cleanup() throws Exception { graph.close(); }
80
+ +
81
+ + @Test void uuidPaginationFiltersRemovedRepositoriesBeforeApplyingTheLimit() {
82
+ + addArtifact("a", "removed", 1);
83
+ + addArtifact("b", "r1", 2);
84
+ + addArtifact("c", "r2", 3);
85
+ + graph.addVertex(T.label, Vertices.ARTIFACT, Properties.UUID, "invalid", Properties.CREATED, 0L);
86
+ + assertEquals(List.of("b", "c"), ids(batch(2, null, null, null, null, null, null, null)));
87
+ + assertEquals(List.of("c"), ids(batch(2, "b", null, null, null, null, null, null)));
88
+ + assertTrue(batch(2, "c", null, null, null, null, null, null).isEmpty());
89
+ + }
90
+ +
91
+ + @Test void selectedAndAuthorizedRepositoriesConstrainTheQueryWithoutChangingTheInput() {
92
+ + addArtifact("a", "r1", 1);
93
+ + addArtifact("b", "r2", 2);
94
+ + List<String> authorized = new ArrayList<>(List.of("s-r2", "s-removed"));
95
+ + assertEquals(List.of("b"), ids(batch(10, null, null, null, authorized, null, null, null)));
96
+ + assertEquals(List.of("s-r2", "s-removed"), authorized);
97
+ + assertEquals(List.of("a"), ids(batch(10, null, "s", "r1", null, null, null, null)));
98
+ + assertTrue(batch(10, null, "s", "missing", null, null, null, null).isEmpty());
99
+ + assertTrue(batch(10, null, null, null, List.of("s-removed"), null, null, null).isEmpty());
100
+ + assertTrue(batch(10, null, null, null, List.of(), null, null, null).isEmpty());
101
+ + }
102
+ +
103
+ + @Test void groupExportsIntersectMembersWithTheOwnersAuthorizedRepositories() {
104
+ + Repository group = addRepository("group", "group");
105
+ + doAnswer(c -> {
106
+ + c.<List<String>>getArgument(1).addAll(List.of("r1", "r2"));
107
+ + return null;
108
+ + }).when(manager).resolveGroupRepository(eq(group), anyList());
109
+ + addArtifact("a", "r1", 1);
110
+ + addArtifact("b", "r2", 2);
111
+ + assertEquals(List.of("a", "b"), ids(batch(10, null, "s", "group", null, null, null, null)));
112
+ + assertEquals(List.of("b"), ids(batch(10, null, "s", "group", List.of("s-r2"), null, null, null)));
113
+ + assertTrue(batch(10, null, "s", "group", List.of("s-other"), null, null, null).isEmpty());
114
+ + }
115
+ +
116
+ + @Test void sortedPagesUseUuidToBreakTiesInBothDirectionsForEverySupportedField() {
117
+ + addArtifact("a", "r1", 2);
118
+ + addArtifact("b", "r1", 1);
119
+ + addArtifact("c", "r1", 2);
120
+ + addArtifact("d", "r2", 3);
121
+ + for (String field : List.of(Properties.LAST_USED, Properties.DOWNLOAD_COUNT,
122
+ + Properties.SIZE_IN_BYTES, Properties.VULNERABILITIES_COUNT)) {
123
+ + assertEquals(List.of("b", "a"), ids(batch(2, null, null, null, null, field, "asc", null)), field);
124
+ + assertEquals(List.of("c", "d"), ids(batch(2, "a", null, null, null, field, "asc", 2L)), field);
125
+ + assertEquals(List.of("d", "a"), ids(batch(2, null, null, null, null, field, "desc", null)), field);
126
+ + assertEquals(List.of("c", "b"), ids(batch(2, "a", null, null, null, field, "desc", 2L)), field);
127
+ + }
128
+ + }
129
+ +
130
+ + @Test void sortedContinuationRejectsVerticesWithoutUuid() {
131
+ + addArtifact("a", "r1", 2);
132
+ + addArtifact("b", "r1", 3);
133
+ + Vertex missingUuid = addArtifact("temporary", "r1", 3);
134
+ + missingUuid.property(Properties.UUID).remove();
135
+ + assertEquals(List.of("b"), ids(batch(10, "a", null, null, null, Properties.DOWNLOAD_COUNT, "asc", 2L)));
136
+ + }
137
+ +
138
+ + @Test void invalidBatchSizeSortAndIncompleteCursorFailBeforeReturningData() {
139
+ + for (int limit : List.of(0, 1001)) {
140
+ + assertThrows(IllegalArgumentException.class, () -> batch(limit, null, null, null, null, null, null, null));
141
+ + }
142
+ + assertThrows(IllegalArgumentException.class, () -> batch(10, null, null, null, null, "uuid", "asc", null));
143
+ + assertThrows(IllegalArgumentException.class, () -> batch(10, null, null, null, null, Properties.DOWNLOAD_COUNT, "invalid", null));
144
+ + assertThrows(IllegalArgumentException.class, () -> batch(10, "a", null, null, null, Properties.DOWNLOAD_COUNT, "asc", null));
145
+ + }
146
+ +
147
+ + @Test void emptyConfigurationReturnsNoArtifacts() {
148
+ + when(configuration.getStorages()).thenReturn(Map.of());
149
+ + assertTrue(batch(10, null, null, null, null, null, null, null).isEmpty());
150
+ + }
151
+ +
152
+ + @Test void interruptedExportStopsBeforeOpeningTheGraph() {
153
+ + Thread.currentThread().interrupt();
154
+ + try {
155
+ + assertThrows(CancellationException.class, () -> batch(10, null, null, null, null, null, null, null));
156
+ + } finally { Thread.interrupted(); }
157
+ + }
158
+ +
159
+ + @Test void sortValuesPreserveNullsAndConvertDatesAndIntegerCounts() {
160
+ + ArtifactEntity artifact = new ArtifactEntity();
161
+ + for (String field : List.of(Properties.LAST_USED, Properties.DOWNLOAD_COUNT,
162
+ + Properties.SIZE_IN_BYTES, Properties.VULNERABILITIES_COUNT)) {
163
+ + assertTrue(ArtifactRepository.isScanExportSortField(field));
164
+ + assertNull(ArtifactRepository.scanExportSortValue(artifact, field));
165
+ + }
166
+ + LocalDateTime date = LocalDateTime.of(2026, 9, 1, 12, 0);
167
+ + artifact.setLastUsed(date);
168
+ + artifact.setDownloadCount(2);
169
+ + artifact.setSizeInBytes(3L);
170
+ + artifact.setVulnerabilitiesCount(4);
171
+ + assertEquals(EntityTraversalUtils.toLong(date), ArtifactRepository.scanExportSortValue(artifact, Properties.LAST_USED));
172
+ + assertEquals(2L, ArtifactRepository.scanExportSortValue(artifact, Properties.DOWNLOAD_COUNT));
173
+ + assertEquals(3L, ArtifactRepository.scanExportSortValue(artifact, Properties.SIZE_IN_BYTES));
174
+ + assertEquals(4L, ArtifactRepository.scanExportSortValue(artifact, Properties.VULNERABILITIES_COUNT));
175
+ + assertFalse(ArtifactRepository.isScanExportSortField(null));
176
+ + assertFalse(ArtifactRepository.isScanExportSortField("uuid"));
177
+ + assertThrows(IllegalArgumentException.class, () -> ArtifactRepository.scanExportSortValue(artifact, "uuid"));
178
+ + }
179
+ +
180
+ + private Repository addRepository(String id, String type) {
181
+ + Repository repository = mock(Repository.class);
182
+ + when(repository.getId()).thenReturn(id);
183
+ + when(repository.getStorage()).thenReturn(storage);
184
+ + when(repository.getType()).thenReturn(type);
185
+ + when(repository.isInService()).thenReturn(true);
186
+ + when(repository.isAllowsDirectoryBrowsing()).thenReturn(true);
187
+ + repositories.put(id, repository);
188
+ + return repository;
189
+ + }
190
+ +
191
+ + private Vertex addArtifact(String uuid, String repositoryId, int rank) {
192
+ + return graph.addVertex(T.label, Vertices.ARTIFACT, Properties.UUID, uuid,
193
+ + Properties.STORAGE_ID, "s", Properties.REPOSITORY_ID, repositoryId,
194
+ + Properties.STORAGE_ID_AND_REPOSITORY_ID, "s-" + repositoryId,
195
+ + Properties.CREATED, 0L, Properties.LAST_USED, (long) rank,
196
+ + Properties.DOWNLOAD_COUNT, rank, Properties.SIZE_IN_BYTES, (long) rank,
197
+ + Properties.VULNERABILITIES_COUNT, rank);
198
+ + }
199
+ +
200
+ + private List<Artifact> batch(int limit, String after, String storageId, String repositoryId,
201
+ + List<String> authorized, String field, String order, Long value) {
202
+ + if (field == null && order == null) {
203
+ + return artifacts.findScanExportBatch(limit, after, false, null, null, storageId, repositoryId,
204
+ + null, authorized, null, null, null, null, null, null, null);
205
+ + }
206
+ + return artifacts.findScanExportBatch(limit, after, false, null, null, storageId, repositoryId,
207
+ + null, authorized, null, null, null, null, null, null, null, field, order, value);
208
+ + }
209
+ +
210
+ + private static List<String> ids(List<Artifact> values) {
211
+ + return values.stream().map(Artifact::getUuid).collect(Collectors.toList());
212
+ + }
213
+ +}
214
+ diff --git a/folib-storage/folib-storage-api/src/test/java/com/veadan/folib/repositories/ArtifactRepositoryTest.java b/folib-storage/folib-storage-api/src/test/java/com/veadan/folib/repositories/ArtifactRepositoryTest.java
215
+ index 1bdbaf0ff1b8d9f3a56a1dac26373e46225565dd..68d7b3d396ece7f871d40f8983bc1b1559cda621 100644
216
+ --- a/folib-storage/folib-storage-api/src/test/java/com/veadan/folib/repositories/ArtifactRepositoryTest.java
217
+ +++ b/folib-storage/folib-storage-api/src/test/java/com/veadan/folib/repositories/ArtifactRepositoryTest.java
218
+ @@ -489,38 +489,30 @@ class ArtifactRepositoryTest {
219
+ List<String> vulnerabilities = Lists.newArrayList("critical", "high");
220
+
221
+ lenient().when(entityTraversalSource.V()).thenReturn(entityTraversal);
222
+ lenient().when(entityTraversal.hasLabel(Vertices.ARTIFACT)).thenReturn(entityTraversal);
223
+ lenient().when(entityTraversal.has(anyString(), any(P.class))).thenReturn(entityTraversal);
224
+ + when(entityTraversal.or(any(EntityTraversal[].class))).thenReturn(entityTraversal);
225
+ lenient().when(entityTraversal.count()).thenReturn(countTraversal);
226
+ lenient().when(countTraversal.tryNext()).thenReturn(Optional.of(3L));
227
+ lenient().when(entityTraversal.range(0L, 10L)).thenReturn(entityTraversal);
228
+ lenient().when(entityTraversal.filter(any(Predicate.class))).thenReturn(entityTraversal);
229
+ - lenient().when(entityTraversal.map(any(Function.class))).thenReturn(artifactTraversalObject);
230
+ - lenient().when(artifactTraversalObject.toList()).thenReturn(Lists.newArrayList(mock(Artifact.class)));
231
+ + EntityTraversal fold = mock(EntityTraversal.class);
232
+ + when(artifactAdapter.searchFold(any())).thenReturn(fold);
233
+ + when(entityTraversal.map(fold)).thenReturn(artifactTraversalObject);
234
+ + List<Artifact> expected = Lists.newArrayList(mock(Artifact.class), mock(Artifact.class), mock(Artifact.class));
235
+ + when(artifactTraversalObject.toList()).thenReturn(expected);
236
+
237
+ - try {
238
+ - java.lang.reflect.Method gMethod = ArtifactRepository.class.getDeclaredMethod("g");
239
+ - gMethod.setAccessible(true);
240
+ - lenient().when(gMethod.invoke(artifactRepository)).thenReturn(entityTraversalSource);
241
+ - } catch (Exception e) {
242
+ - // Ignore exception
243
+ - }
244
+ -
245
+ - //lenient().when
246
+ - Page<Artifact> result = null;
247
+ - try {
248
+ - result = artifactRepository.findMatchingByIndex(
249
+ - pageable, false, null, null, null, null,
250
+ - null, null, null, null, null, vulnerabilities, null, null, null, null, null);
251
+ - } catch (Exception e) {
252
+ - log.error("Error:", e);
253
+ - }
254
+ + Page<Artifact> result = artifactRepository.findMatchingByIndex(
255
+ + pageable, false, null, null, null, null,
256
+ + null, null, null, null, null, vulnerabilities, null, null, null, null, null);
257
+
258
+ // Then
259
+ - assertNull(result);
260
+ -// assertEquals(3, result.getTotalElements());
261
+ + assertNotNull(result);
262
+ + assertEquals(3, result.getTotalElements());
263
+ + assertEquals(expected, result.getContent());
264
+ + verify(entityTraversal, times(2)).or(any(EntityTraversal[].class));
265
+ }
266
+
267
+ // @Test
268
+ void testFindMatchingByIndex_WithDateRange() {
269
+ // Given
270
+ diff --git a/folib-web-core/SCAN_EXPORT.md b/folib-web-core/SCAN_EXPORT.md
271
+ index cc6bb10d0a067a96f9e80cf54f8b73dc37aa50e1..cfbfb86a6ed5c40380d46f48fd5ca3a8b152afb5 100644
272
+ --- a/folib-web-core/SCAN_EXPORT.md
273
+ +++ b/folib-web-core/SCAN_EXPORT.md
274
+ @@ -113,14 +113,24 @@ workers 范围为 1 至 8;page-size 范围为 1 至 1000;三个超时均至
275
+ ## 验证命令与验收
276
+
277
+ 从项目根目录执行后端定向测试及模块打包(离线模式要求本地依赖已准备好):
278
+
279
+ ```sh
280
+ -mvn -q -o --settings folib-settings.xml -pl folib-web-core -am -Dfolib.test.mockMaker=mock-maker-inline -Dtest=ArtifactScanExportAccessTest,ArtifactScanExportServiceTest,ArtifactScanExportStoreTest,ArtifactScanReportWriterTest,ArtifactScanExportProjectionTest,ArtifactScanExportSqlTest,ScanExportSeedTest,ArtifactScanExportSettingsTest,ArtifactScanExportParityTest,ArtifactScanExportBackgroundAuthTest,AqlControllerTest,FqlSearchServiceTest,ExtendedAuthoritiesVoterTest -Dsurefire.failIfNoSpecifiedTests=false -DfailIfNoTests=false -Dspring-boot.repackage.skip=true package
281
+ +mvn -q -o --settings folib-settings.xml -pl folib-web-core -am '-Dtest=ArtifactRepositoryScanExportTest,ArtifactRepositoryTest#testFindMatchingByIndex_WithVulnerabilities,ArtifactScanExport*Test,ArtifactScanReportWriterTest,ScanExportSeedTest,AqlControllerTest' -Dsurefire.failIfNoSpecifiedTests=false -DfailIfNoTests=false -Dspring-boot.repackage.skip=true package
282
+ ```
283
+
284
+ -完整回归包含静态模拟测试,需要支持 Java agent 的 JDK,并通过 folib.test.mockMaker 参数显式选择 inline;不改变项目普通测试的默认 MockMaker。验收工具 prepare 仅运行其定义的 7 个测试类,不替代这里的完整回归。
285
+ +此命令覆盖本功能的 10 个 web 测试类、storage 模块的 ArtifactRepositoryScanExportTest、修复后的漏洞筛选用例,以及原有 AqlControllerTest。测试资源固定使用原有的 SubclassByteBuddyMockMaker;新增测试不使用静态模拟,无需切换 inline 或设置 folib.test.mockMaker。web 模块沿用现有 JaCoCo 配置,报告位于 folib-web-core/target/site/jacoco/index.html。
286
+ +
287
+ +ArtifactRepository 的直接测试放在所属的 folib-storage-api 模块,避免依赖跨模块覆盖率汇总。单独验证该模块的测试可执行以下命令;覆盖率仍由现有 CI 流程注入 agent 和收集报告,不修改 storage 的 Surefire 或打包配置:
288
+ +
289
+ +```sh
290
+ +mvn -q -o --settings folib-settings.xml -pl folib-storage/folib-storage-api '-Dtest=ArtifactRepositoryScanExportTest,ArtifactRepositoryTest#testFindMatchingByIndex_WithVulnerabilities' test
291
+ +```
292
+ +
293
+ +2026-10-10 本地验证:web 侧回归 141 项通过(含上述 10 类导出测试的 77 项),storage 侧 9 项新增查询测试和 1 项漏洞筛选测试全部通过。本地临时通过命令行传入 JaCoCo agent 后,独立 storage 执行数据生成的报告中,ArtifactRepository.java 第 205–277 行的 49 个可执行行全部命中;分支覆盖 74/81,尚未达到全部分支覆盖。该统计是本地验证结果,不代表 Jenkins 已完成同样的覆盖率收集。
294
+ +
295
+ +验收工具 prepare 仅运行其定义的 7 个测试类,不替代上述回归。历史命令中的 FqlSearchServiceTest、ExtendedAuthoritiesVoterTest 仍包含原有静态模拟,不属于此处的默认 MockMaker 回归范围。
296
+
297
+ 这里必须使用 package,而非仅使用 test:folib-data-service 的资源解包需要上游模块已生成 JAR,仅执行 test 会在到达导出测试之前报 MDEP-98。此命令不跳过定向测试;spring-boot.repackage.skip 只跳过可执行 Boot JAR 的重打包,不代表生成了可部署的完整发行包。
298
+
299
+ 前端回归测试使用 Node 20 的原生测试运行器和 VM 模块,不增加测试框架,也不会访问实际服务器。行为用例读取真实组件脚本、筛选函数、API 模块和鉴权拦截器,仅模拟外部接口;两条模板用例使用项目已有的 vue-template-compiler 编译扫描页和抽屉,并检查生成的渲染函数语法。模板测试需要已安装项目依赖,不替代完整 Webpack 构建或浏览器端到端测试。
300
+
301
+ diff --git a/folib-web-core/pom.xml b/folib-web-core/pom.xml
302
+ index f706de2f9f2dccf413cc8acf859c29d429df8c0b..5324c0f3576f14d0c450a1b7d7c44debe86744b3 100644
303
+ --- a/folib-web-core/pom.xml
304
+ +++ b/folib-web-core/pom.xml
305
+ @@ -15,38 +15,17 @@
306
+ <artifactId>folib-web-core</artifactId>
307
+ <packaging>jar</packaging>
308
+ <name>Folib: Web Core</name>
309
+ <inceptionYear>2019</inceptionYear>
310
+
311
+ - <properties>
312
+ - <!-- Keep agent-free tests as the default; static-mock suites opt in with -Dfolib.test.mockMaker=mock-maker-inline. -->
313
+ - <folib.test.mockMaker>org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker</folib.test.mockMaker>
314
+ - </properties>
315
+ -
316
+ <scm>
317
+ <url>http://git.folib.com/folib/folib-server</url>
318
+ <connection>scm:git:git://git.folib.com/folib/folib-server.git</connection>
319
+ <developerConnection>scm:git:git://git.folib.com/folib/folib-server.git</developerConnection>
320
+ </scm>
321
+
322
+ <build>
323
+ - <testResources>
324
+ - <testResource>
325
+ - <directory>src/test/resources</directory>
326
+ - <filtering>false</filtering>
327
+ - <excludes>
328
+ - <exclude>mockito-extensions/org.mockito.plugins.MockMaker</exclude>
329
+ - </excludes>
330
+ - </testResource>
331
+ - <testResource>
332
+ - <directory>src/test/resources</directory>
333
+ - <filtering>true</filtering>
334
+ - <includes>
335
+ - <include>mockito-extensions/org.mockito.plugins.MockMaker</include>
336
+ - </includes>
337
+ - </testResource>
338
+ - </testResources>
339
+ <plugins>
340
+ <plugin>
341
+ <groupId>org.apache.maven.plugins</groupId>
342
+ <artifactId>maven-clean-plugin</artifactId>
343
+ <version>3.1.0</version>
344
+ diff --git a/folib-web-core/src/test/java/com/veadan/folib/export/ArtifactScanExportBackgroundAuthTest.java b/folib-web-core/src/test/java/com/veadan/folib/export/ArtifactScanExportBackgroundAuthTest.java
345
+ index b17c44c8506a48675fb72b12a546d4bcc256baf5..ae2529c8ba25217d783b4ab9d4ad42e2fa672dd2 100644
346
+ --- a/folib-web-core/src/test/java/com/veadan/folib/export/ArtifactScanExportBackgroundAuthTest.java
347
+ +++ b/folib-web-core/src/test/java/com/veadan/folib/export/ArtifactScanExportBackgroundAuthTest.java
348
+ @@ -1,14 +1,19 @@
349
+ package com.veadan.folib.export;
350
+
351
+ +import ch.qos.logback.classic.Level;
352
+ +import ch.qos.logback.classic.Logger;
353
+ +import ch.qos.logback.classic.spi.ILoggingEvent;
354
+ +import ch.qos.logback.core.read.ListAppender;
355
+ import com.fasterxml.jackson.databind.ObjectMapper;
356
+ import com.veadan.folib.components.auth.AuthComponent;
357
+ import com.veadan.folib.configuration.ConfigurationManager;
358
+ import com.veadan.folib.configuration.ConfigurationUtils;
359
+ -import com.veadan.folib.providers.io.RepositoryFiles;
360
+ +import com.veadan.folib.providers.io.LayoutFileSystem;
361
+ import com.veadan.folib.providers.io.RepositoryPath;
362
+ import com.veadan.folib.providers.io.RepositoryPathResolver;
363
+ +import com.veadan.folib.providers.io.RootRepositoryPath;
364
+ import com.veadan.folib.security.resolvepath.ResolvePathProviderRegistry;
365
+ import com.veadan.folib.security.vote.ExtendedAuthoritiesVoter;
366
+ import com.veadan.folib.storage.Storage;
367
+ import com.veadan.folib.storage.repository.Repository;
368
+ import com.veadan.folib.users.domain.Privileges;
369
+ @@ -16,10 +21,12 @@ import com.veadan.folib.users.userdetails.SpringSecurityUser;
370
+ import com.veadan.folib.util.CacheUtil;
371
+ import com.veadan.folib.utils.UrlUtils;
372
+ import org.junit.jupiter.api.AfterEach;
373
+ import org.junit.jupiter.api.BeforeEach;
374
+ import org.junit.jupiter.api.Test;
375
+ +import org.junit.jupiter.api.io.TempDir;
376
+ +import org.slf4j.LoggerFactory;
377
+ import org.springframework.mock.web.MockHttpServletRequest;
378
+ import org.springframework.security.access.AccessDeniedException;
379
+ import org.springframework.security.core.GrantedAuthority;
380
+ import org.springframework.security.core.context.SecurityContext;
381
+ import org.springframework.security.core.context.SecurityContextHolder;
382
+ @@ -27,10 +34,11 @@ import org.springframework.security.core.userdetails.UserDetailsService;
383
+ import org.springframework.test.util.ReflectionTestUtils;
384
+ import org.springframework.web.context.request.RequestAttributes;
385
+ import org.springframework.web.context.request.RequestContextHolder;
386
+ import org.springframework.web.context.request.ServletRequestAttributes;
387
+
388
+ +import java.nio.file.Path;
389
+ import java.util.ArrayList;
390
+ import java.util.HashMap;
391
+ import java.util.List;
392
+ import java.util.Map;
393
+ import java.util.Set;
394
+ @@ -40,10 +48,11 @@ import java.util.concurrent.TimeUnit;
395
+ import static org.junit.jupiter.api.Assertions.*;
396
+ import static org.mockito.ArgumentMatchers.*;
397
+ import static org.mockito.Mockito.*;
398
+
399
+ class ArtifactScanExportBackgroundAuthTest {
400
+ + @TempDir Path directory;
401
+ private RequestAttributes previousRequest;
402
+ private SecurityContext previousSecurity;
403
+
404
+ @BeforeEach void isolateThreadContext() {
405
+ previousRequest = RequestContextHolder.getRequestAttributes();
406
+ @@ -55,14 +64,25 @@ class ArtifactScanExportBackgroundAuthTest {
407
+ @AfterEach void restoreThreadContext() {
408
+ RequestContextHolder.setRequestAttributes(previousRequest);
409
+ SecurityContextHolder.setContext(previousSecurity);
410
+ }
411
+
412
+ - @Test void absentRequestIsOptionalWithoutInvokingTheThrowingAccessor() {
413
+ - try (var holder = mockStatic(RequestContextHolder.class, CALLS_REAL_METHODS)) {
414
+ + @Test void absentRequestIsOptionalWithoutLoggingExceptions() {
415
+ + Logger logger = (Logger) LoggerFactory.getLogger(UrlUtils.class);
416
+ + Level previousLevel = logger.getLevel();
417
+ + ListAppender<ILoggingEvent> events = new ListAppender<>();
418
+ + events.start();
419
+ + logger.setLevel(Level.ERROR);
420
+ + logger.addAppender(events);
421
+ + try {
422
+ for (int i = 0; i < 1000; i++) assertNull(UrlUtils.getRequest());
423
+ - holder.verify(RequestContextHolder::currentRequestAttributes, never());
424
+ + assertTrue(events.list.isEmpty(), "An absent request must not log an exception on each lookup");
425
+ + assertThrows(IllegalStateException.class, RequestContextHolder::currentRequestAttributes);
426
+ + } finally {
427
+ + logger.detachAppender(events);
428
+ + logger.setLevel(previousLevel);
429
+ + events.stop();
430
+ }
431
+ }
432
+
433
+ @Test void realServletRequestAndStrictUriAccessorAreUnchanged() {
434
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/storages/s/r/file.jar");
435
+ @@ -121,20 +141,20 @@ class ArtifactScanExportBackgroundAuthTest {
436
+ rights.put(prefix + "readable/a.jar", Set.of(Privileges.ARTIFACTS_RESOLVE));
437
+ rights.put(prefix + "reports/result.xlsx", Set.of(Privileges.ARTIFACTS_RESOLVE, Privileges.ARTIFACTS_DEPLOY));
438
+ when(user.getStorageAuthorities(anyString(), eq(false)))
439
+ .thenAnswer(call -> rights.getOrDefault(call.getArgument(0), Set.of()));
440
+ ArtifactScanExportAccess access = new ArtifactScanExportAccess(configuration, paths, auth, users, new ObjectMapper());
441
+ - CacheUtil<String, Repository> cache = mock(CacheUtil.class);
442
+ - when(cache.get(storageId + ":" + repositoryId)).thenReturn(repository);
443
+ - try (var cacheFactory = mockStatic(CacheUtil.class);
444
+ - var files = mockStatic(RepositoryFiles.class);
445
+ - var holder = mockStatic(RequestContextHolder.class, CALLS_REAL_METHODS)) {
446
+ - cacheFactory.when(CacheUtil::getInstance).thenReturn(cache);
447
+ + LayoutFileSystem fileSystem = mock(LayoutFileSystem.class);
448
+ + when(fileSystem.getRootDirectory()).thenReturn(new RootRepositoryPath(directory, fileSystem));
449
+ + CacheUtil<String, Repository> cache = CacheUtil.getInstance();
450
+ + String cacheKey = storageId + ":" + repositoryId;
451
+ + Repository previousRepository = cache.get(cacheKey);
452
+ + cache.put(cacheKey, repository);
453
+ + try {
454
+ for (String path : List.of("readable/a.jar", "denied/b.jar", "reports/result.xlsx")) {
455
+ - RepositoryPath resolved = mock(RepositoryPath.class);
456
+ + RepositoryPath resolved = new RepositoryPath(directory.resolve(path), fileSystem);
457
+ when(paths.resolve(storageId, repositoryId, path)).thenReturn(resolved);
458
+ - files.when(() -> RepositoryFiles.relativizePath(resolved)).thenReturn(path);
459
+ }
460
+ access.assumeOwner("export-user");
461
+ var identity = SecurityContextHolder.getContext().getAuthentication();
462
+ assertEquals("export-user", access.owner());
463
+ for (int i = 0; i < 200; i++) {
464
+ @@ -147,12 +167,14 @@ class ArtifactScanExportBackgroundAuthTest {
465
+ rights.clear();
466
+ assertFalse(access.canReadArtifact(storageId, repositoryId, "readable/a.jar"));
467
+ assertThrows(AccessDeniedException.class, () -> access.target(storageId, repositoryId, "reports/result.xlsx", true));
468
+ assertSame(identity, SecurityContextHolder.getContext().getAuthentication());
469
+ assertNull(RequestContextHolder.getRequestAttributes());
470
+ - holder.verify(RequestContextHolder::currentRequestAttributes, never());
471
+ verify(user, atLeastOnce()).getStorageAuthorities(prefix + "denied/b.jar", false);
472
+ + } finally {
473
+ + if (previousRepository == null) cache.remove(cacheKey);
474
+ + else cache.put(cacheKey, previousRepository);
475
+ }
476
+ }
477
+
478
+ @Test void webRequestStillAppliesTheExistingRestrictedHostRule() {
479
+ ExtendedAuthoritiesVoter voter = spy(new ExtendedAuthoritiesVoter());
480
+ diff --git a/folib-web-core/src/test/java/com/veadan/folib/export/ArtifactScanExportParityTest.java b/folib-web-core/src/test/java/com/veadan/folib/export/ArtifactScanExportParityTest.java
481
+ index f10eceb5832b7e23053c02643ae9003299ef384d..d399b1ce575c3eadc923b2a472f284defcffb89f 100644
482
+ --- a/folib-web-core/src/test/java/com/veadan/folib/export/ArtifactScanExportParityTest.java
483
+ +++ b/folib-web-core/src/test/java/com/veadan/folib/export/ArtifactScanExportParityTest.java
484
+ @@ -6,12 +6,15 @@ import com.veadan.folib.components.common.CommonComponent;
485
+ import com.veadan.folib.components.repository.RepositoryComponent;
486
+ import com.veadan.folib.controllers.aql.AqlController;
487
+ import com.veadan.folib.domain.Artifact;
488
+ import com.veadan.folib.domain.Component;
489
+ import com.veadan.folib.domain.Vulnerability;
490
+ +import com.veadan.folib.providers.io.LayoutFileSystem;
491
+ +import com.veadan.folib.providers.io.RepositoryFileAttributeType;
492
+ import com.veadan.folib.providers.io.RepositoryFiles;
493
+ import com.veadan.folib.providers.io.RepositoryPath;
494
+ +import com.veadan.folib.providers.layout.LayoutFileSystemProvider;
495
+ import com.veadan.folib.repositories.ArtifactRepository;
496
+ import com.veadan.folib.repositories.ComponentRepository;
497
+ import com.veadan.folib.repositories.VulnerabilityRepository;
498
+ import com.veadan.folib.services.impl.ComponentServiceImpl;
499
+ import com.veadan.folib.services.impl.FqlSearchService;
500
+ @@ -25,11 +28,11 @@ import org.springframework.data.domain.PageImpl;
501
+ import org.springframework.data.domain.Pageable;
502
+ import org.springframework.mock.web.MockHttpServletResponse;
503
+ import org.springframework.test.util.ReflectionTestUtils;
504
+
505
+ import java.io.ByteArrayInputStream;
506
+ -import java.net.URL;
507
+ +import java.nio.file.LinkOption;
508
+ import java.nio.file.Path;
509
+ import java.time.LocalDateTime;
510
+ import java.util.*;
511
+
512
+ import static org.junit.jupiter.api.Assertions.*;
513
+ @@ -49,14 +52,19 @@ class ArtifactScanExportParityTest {
514
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
515
+ try {
516
+ Repository repository = mock(Repository.class);
517
+ when(repository.getLayout()).thenReturn(docker ? "Docker" : "Raw");
518
+ when(repository.getOwner()).thenReturn("project-owner");
519
+ - RepositoryPath repositoryPath = mock(RepositoryPath.class);
520
+ - when(repositoryPath.getRepository()).thenReturn(repository);
521
+ - when(repositoryPath.toAbsolutePath()).thenReturn(repositoryPath);
522
+ - when(repositoryPath.toString()).thenReturn("/acceptance/file.jar");
523
+ + LayoutFileSystem fileSystem = mock(LayoutFileSystem.class);
524
+ + LayoutFileSystemProvider provider = mock(LayoutFileSystemProvider.class);
525
+ + when(fileSystem.getRepository()).thenReturn(repository);
526
+ + when(fileSystem.provider()).thenReturn(provider);
527
+ + RepositoryPath repositoryPath = new RepositoryPath(directory.resolve("file.jar"), fileSystem);
528
+ + when(provider.readAttributes(eq(repositoryPath),
529
+ + eq(RepositoryFiles.formatAttributes(RepositoryFileAttributeType.RESOURCE_URL)), any(LinkOption[].class)))
530
+ + .thenReturn(Map.of(RepositoryFileAttributeType.RESOURCE_URL.getName(),
531
+ + repositoryPath.getTarget().toUri().toURL()));
532
+ FqlSearchService search = ScanExportPresentationFixture.search(repositoryPath);
533
+ ArtifactRepository artifacts = mock(ArtifactRepository.class);
534
+ ComponentRepository components = mock(ComponentRepository.class);
535
+ VulnerabilityRepository vulnerabilities = mock(VulnerabilityRepository.class);
536
+ CommonComponent common = mock(CommonComponent.class);
537
+ @@ -113,15 +121,12 @@ class ArtifactScanExportParityTest {
538
+ AqlController legacy = new AqlController();
539
+ ReflectionTestUtils.setField(legacy, "fqlSearchService", search);
540
+ ReflectionTestUtils.setField(legacy, "componentService", componentService);
541
+ ReflectionTestUtils.setField(legacy, "vulnerabilityWebService", vulnerabilityService);
542
+ MockHttpServletResponse response = new MockHttpServletResponse();
543
+ - try (var files = mockStatic(RepositoryFiles.class)) {
544
+ - files.when(() -> RepositoryFiles.readResourceUrl(repositoryPath)).thenReturn(new URL("file:/acceptance/file.jar"));
545
+ - legacy.exportArtifactComponentVulnerability(null, null, false, "storage", "source", null, null,
546
+ - null, null, null, status, null, null, null, null, 10, 1, response);
547
+ - }
548
+ + legacy.exportArtifactComponentVulnerability(null, null, false, "storage", "source", null, null,
549
+ + null, null, null, status, null, null, null, null, 10, 1, response);
550
+ ArtifactScanExportAccess access = mock(ArtifactScanExportAccess.class);
551
+ when(access.isAdmin()).thenReturn(true);
552
+ when(access.canReadArtifact(anyString(), anyString(), anyString())).thenReturn(true);
553
+ ArtifactScanReportWriter writer = new ArtifactScanReportWriter(artifacts, components, vulnerabilities,
554
+ mock(RepositoryComponent.class), access, search);
555
+ diff --git a/folib-web-core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/folib-web-core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
556
+ index 8b43cbaa00f32ddd60f74282d2015b709269a21a..ca8778f4f8e0ca2e2ecf3ca86d36f18dfd882da9 100644
557
+ --- a/folib-web-core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
558
+ +++ b/folib-web-core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
559
+ @@ -1 +1 @@
560
+ -${folib.test.mockMaker}
561
+ +org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker