From 52be2cc7b1f0b2c6bed589dd4bca40857a648094 Mon Sep 17 00:00:00 2001 From: dengjun Date: Thu, 7 Dec 2023 14:24:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20fileShare=E5=A2=9E=E5=8A=A0TDD=E7=94=A8?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: dengjun --- bundle.json | 3 +- interfaces/test/BUILD.gn | 20 + .../test/unittest/src/file_share/BUILD.gn | 21 + .../unittest/src/file_share/FileShare.test.js | 463 ++++++++++++++++++ .../test/unittest/src/file_share/config.json | 76 +++ .../src/file_share/openharmony_sx.p7b | Bin 0 -> 3585 bytes 6 files changed, 582 insertions(+), 1 deletion(-) create mode 100644 interfaces/test/BUILD.gn create mode 100644 interfaces/test/unittest/src/file_share/BUILD.gn create mode 100644 interfaces/test/unittest/src/file_share/FileShare.test.js create mode 100644 interfaces/test/unittest/src/file_share/config.json create mode 100644 interfaces/test/unittest/src/file_share/openharmony_sx.p7b diff --git a/bundle.json b/bundle.json index 0d0c382c7..0b6996b7a 100644 --- a/bundle.json +++ b/bundle.json @@ -124,7 +124,8 @@ "test": [ "//foundation/filemanagement/app_file_service/test/fuzztest:fuzztest", "//foundation/filemanagement/app_file_service/test/unittest:unittest", - "//foundation/filemanagement/app_file_service:tgt_backup_tests" + "//foundation/filemanagement/app_file_service:tgt_backup_tests", + "//foundation/filemanagement/app_file_service/interfaces/test:unittest" ] } } diff --git a/interfaces/test/BUILD.gn b/interfaces/test/BUILD.gn new file mode 100644 index 000000000..6917c4039 --- /dev/null +++ b/interfaces/test/BUILD.gn @@ -0,0 +1,20 @@ +# Copyright (c) 2023 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import("//build/test.gni") + +group("unittest") { + testonly = true + deps = [ + "unittest/src/file_share:FileShareJSTest", + ] +} diff --git a/interfaces/test/unittest/src/file_share/BUILD.gn b/interfaces/test/unittest/src/file_share/BUILD.gn new file mode 100644 index 000000000..44075e560 --- /dev/null +++ b/interfaces/test/unittest/src/file_share/BUILD.gn @@ -0,0 +1,21 @@ +# Copyright (C) 2023 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import("//build/test.gni") + +ohos_js_unittest("FileShareJSTest") { + module_out_path = "filemanagement/app_file_service" + + hap_profile = "./config.json" + + certificate_profile = "openharmony_sx.p7b" +} diff --git a/interfaces/test/unittest/src/file_share/FileShare.test.js b/interfaces/test/unittest/src/file_share/FileShare.test.js new file mode 100644 index 000000000..3c12923a3 --- /dev/null +++ b/interfaces/test/unittest/src/file_share/FileShare.test.js @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {describe, beforeAll, afterAll, it, expect} from 'deccjsunit/index' +import fileShare from '@ohos.fileshare' +import bundleManager from '@ohos.bundle.bundleManager' +import fileuri from '@ohos.file.fileuri' +import fs from '@ohos.file.fs' + +const BUNDLE_NAME = 'ohos.file.share.test'; +const APP_FLAGS = bundleManager.ApplicationFlag.GET_APPLICATION_INFO_WITH_PERMISSION; +const USER_ID = 100; +const DEVICE_TYPE = 801; +const MISS_PARAMETER = 401; +const REAL_URI = "/data/storage/el2/base/test1.txt"; +const INEXISTENT_URI = "/data/storage/el2/base/test2.txt"; + +describe('FileShareJSTest', function () { + beforeAll(async function () { + console.info('beforeAll'); + GetRealUri(); + GetInexistentUri(); + }) + + afterAll(async function () { + console.info('afterAll'); + }) + + function GetTokenId () { + console.info('getApplicationInfo start'); + try { + let data = bundleManager.getApplicationInfo(BUNDLE_NAME, APP_FLAGS, USER_ID); + console.info(0x0000, 'testTag', 'getApplicationInfo successfully. Data: %{public}s', JSON.stringify(data)); + return data.accessTokenId; + } catch(error) { + console.info(0x0000, 'testTag', 'getApplicationInfo failed. Cause: %{public}s', error.message); + } + console.info('getApplicationInfo end'); + } + + function GetRealUri() { + let fileStr = "/data/storage/el2/base/test1.txt"; + let uriObject = new fileuri.FileUri(fileStr); + let uri = uriObject.toString(); + let fd = fs.open(uri, fs.OpenMode.CREATE); + fs.close(fd); + REAL_URI = uri; + } + + function GetInexistentUri() { + let fileStr = "/data/storage/el2/base/test2.txt"; + let uriObject = new fileuri.FileUri(fileStr); + let uri = uriObject.toString(); + INEXISTENT_URI = uri; + } + + function GetRealPolicyInfo() { + let uri = REAL_URI; + let policyInfo = fileShare.PolicyInfo; + policyInfo.uri = uri; + policyInfo.operateMode = 0; + return policyInfo; + } + + function GetInexistentPolicyInfo() { + let uri = INEXISTENT_URI; + let policyInfo = fileShare.PolicyInfo; + policyInfo.uri = uri; + policyInfo.operateMode = 0; + return policyInfo; + } + + /** + * @tc.name: grant_permission_test_001 + * @tc.desc: Grant the selected real URI temporary permissions test + * @tc.type: FUNC test + * @tc.require: issueI#I8KE59 + */ + it('grant_permission_test_001', 0, async function (done) { + console.info(`grant_permission_test_001 start`); + let tokenId = GetTokenId(); + console.info(`tokenId`); + let policyInfo = GetRealPolicyInfo(); + console.info(`policyInfo`); + let policyFlag = fileShare.PolicyFlag.ALLOW_PERSISTENCE; + try { + fileShare.grantPermission(tokenId,policyInfo,policyFlag).then((result) => { + console.info(`grantPermissionTest001 success`); + expect(result.length).assertEqual(0); + done(); + }).catch((err) => { + console.info(`grantPermissionTest001 err : ${err.code}`); + expect(err.code).assertEqual(DEVICE_TYPE); + done(); + }) + } catch (error) { + console.info(`grantPermissionTest001 error : ${error.code}`); + expect(error.code).assertEqual(DEVICE_TYPE); + done(); + } + console.info(`grant_permission_test_001 end`); + }) + + /** + * @tc.name: grant_permission_test_002 + * @tc.desc: Grant the selected Inexistent URI temporary permissions test + * @tc.type: FUNC test + * @tc.require: issueI#I8KE59 + */ + it('grant_permission_test_002', 0, async function (done) { + console.info(`grant_permission_test_002 start`); + let tokenId = GetTokenId(); + let policyInfo = GetInexistentPolicyInfo(); + let policyFlag = fileShare.PolicyFlag.ALLOW_PERSISTENCE; + try { + fileShare.grantPermission(tokenId,policyInfo,policyFlag).then(() => { + console.info(`grantPermissionTest002 Fail`); + expect().assertFail(); + done(); + }).catch((err) => { + console.info(`grantPermissionTest002 success : ${err.code}`); + expect(err.code).assertEqual(DEVICE_TYPE); + done(); + }) + } catch (error) { + console.info(`grantPermissionTest002 success : ${error.code}`); + expect(error.code).assertEqual(DEVICE_TYPE); + done(); + } + console.info(`grant_permission_test_002 end`); + }) + + /** + * @tc.name: grant_permission_test_003 + * @tc.desc: Grant the temporary permissions with missing parameter test + * @tc.type: FUNC test + * @tc.require: issueI#I8KE59 + */ + it('grant_permission_test_003', 0, async function (done) { + console.info(`grantPermission_003 start`); + let tokenId = GetTokenId(); + let policyFlag = fileShare.PolicyFlag.ALLOW_PERSISTENCE; + try { + fileShare.grantPermission(tokenId,policyFlag).then(() => { + console.info(`grantPermissionTest003 Fail`); + expect().assertFail(); + done(); + }).catch((err) => { + console.info(`grantPermissionTest003 success : ${err.code}`); + if (err.code == DEVICE_TYPE) { + expect(err.code).assertEqual(DEVICE_TYPE); + } else { + expect(err.code).assertEqual(MISS_PARAMETER); + } + done(); + }) + } catch (error) { + console.info(`grantPermissionTest003 success : ${error.code}`); + if (error.code == DEVICE_TYPE) { + expect(error.code).assertEqual(DEVICE_TYPE); + } else { + expect(error.code).assertEqual(MISS_PARAMETER); + } + done(); + } + console.info(`grant_permission_test_003 end`); + }) + + /** + * @tc.name: grant_permission_test_004 + * @tc.desc: Grant the temporary permissions with missing parameter test + * @tc.type: FUNC test + * @tc.require: issueI#I8KE59 + */ + it('grant_permission_test_004', 0, async function (done) { + console.info(`grantPermission_004 start`); + let tokenId = GetTokenId(); + try { + fileShare.grantPermission(tokenId).then(() => { + console.info(`grantPermissionTest004 Fail`); + expect().assertFail(); + done(); + }).catch((err) => { + console.info(`grantPermissionTest004 success : ${err.code}`); + if (err.code == DEVICE_TYPE) { + expect(err.code).assertEqual(DEVICE_TYPE); + } else { + expect(err.code).assertEqual(MISS_PARAMETER); + } + done(); + }) + } catch (error) { + console.info(`grantPermissionTest004 success : ${error.code}`); + if (error.code == DEVICE_TYPE) { + expect(error.code).assertEqual(DEVICE_TYPE); + } else { + expect(error.code).assertEqual(MISS_PARAMETER); + } + done(); + } + console.info(`grant_permission_test_004 end`); + }) + + /** + * @tc.name: grant_permission_test_005 + * @tc.desc: Grant the temporary permissions with missing parameter test + * @tc.type: FUNC test + * @tc.require: issueI#I8KE59 + */ + it('grant_permission_test_005', 0, async function (done) { + console.info(`grantPermission_005 start`); + let policyInfo = GetInexistentPolicyInfo(); + try { + fileShare.grantPermission(policyInfo).then(() => { + console.info(`grantPermissionTest005 Fail`); + expect().assertFail(); + done(); + }).catch((err) => { + console.info(`grantPermissionTest005 success : ${err.code}`); + if (err.code == DEVICE_TYPE) { + expect(err.code).assertEqual(DEVICE_TYPE); + } else { + expect(err.code).assertEqual(MISS_PARAMETER); + } + done(); + }) + } catch (error) { + console.info(`grantPermissionTest005 success : ${error.code}`); + if (error.code == DEVICE_TYPE) { + expect(error.code).assertEqual(DEVICE_TYPE); + } else { + expect(error.code).assertEqual(MISS_PARAMETER); + } + done(); + } + console.info(`grant_permission_test_005 end`); + }) + + /** + * @tc.name: persist_permission_test_001 + * @tc.desc: Set persistence permissions for the real URI test + * @tc.type: FUNC test + * @tc.require: issueI#I8KE59 + */ + it('persist_permission_test_001', 0, async function (done) { + console.info(`persist_permission_test_001 start`); + let policyInfo = GetRealPolicyInfo(); + try { + fileShare.persistPermission(policyInfo).then((result) => { + console.info(`persist_permission_test_001 success`); + expect(result.length).assertEqual(0); + done(); + }).catch((err) => { + console.info(`persist_permission_test_001 err : ${err.code}`); + expect(err.code).assertEqual(DEVICE_TYPE); + done(); + }) + } catch (error) { + console.info(`persist_permission_test_001 error : ${error.code}`); + expect(error.code).assertEqual(DEVICE_TYPE); + done(); + } + console.info(`persist_permission_test_001 end`); + }) + + /** + * @tc.name: persist_permission_test_002 + * @tc.desc: Set persistence permissions for the Inexistent URI test + * @tc.type: FUNC test + * @tc.require: issueI#I8KE59 + */ + it('persist_permission_test_002', 0, async function (done) { + console.info(`persist_permission_test_002 start`); + let policyInfo = GetInexistentPolicyInfo(); + try { + fileShare.persistPermission(policyInfo).then(() => { + console.info(`persist_permission_test_002 Fail`); + expect().assertFail(); + done(); + }).catch((err) => { + console.info(`persist_permission_test_002 success: ${err.code}`); + expect(err.code).assertEqual(DEVICE_TYPE); + done(); + }) + } catch (error) { + expect(error.code).assertEqual(DEVICE_TYPE); + done(); + } + console.info(`persist_permission_test_002 end`); + }) + + /** + * @tc.name: revoke_permission_test_001 + * @tc.desc: Revoke persistence permissions for the real URI test + * @tc.type: FUNC test + * @tc.require: issueI#I8KE59 + */ + it('revoke_permission_test_001', 0, async function (done) { + console.info(`revoke_permission_test_001 start`); + let policyInfo = GetRealPolicyInfo(); + try { + fileShare.revokePermission(policyInfo).then((result) => { + console.info(`revoke_permission_test_001 success`); + expect(result.length).assertEqual(0); + done(); + }).catch((err) => { + console.info(`revoke_permission_test_001 err : ${err.code}`); + expect(err.code).assertEqual(DEVICE_TYPE); + done(); + }) + } catch (error) { + console.info(`revoke_permission_test_001 error : ${error.code}`); + expect(error.code).assertEqual(DEVICE_TYPE); + done(); + } + console.info(`revoke_permission_test_001 end`); + }) + + /** + * @tc.name: revoke_permission_test_002 + * @tc.desc: Revoke persistence permissions for the Inexistent URI test + * @tc.type: FUNC test + * @tc.require: issueI#I8KE59 + */ + it('revoke_permission_test_002', 0, async function (done) { + console.info(`revoke_permission_test_002 start`); + let policyInfo = GetInexistentPolicyInfo(); + try { + fileShare.revokePermission(policyInfo).then(() => { + console.info(`persist_permission_test_002 Fail`); + expect().assertFail(); + done(); + }).catch((err) => { + console.info(`persist_permission_test_002 success : ${err.code}`); + expect(err.code).assertEqual(DEVICE_TYPE); + done(); + }) + } catch (error) { + expect(error.code).assertEqual(DEVICE_TYPE); + done(); + } + console.info(`revoke_permission_test_002 end`); + }) + + /** + * @tc.name: activate_permission_test_001 + * @tc.desc: Enable the real URI that have been permanently authorized test + * @tc.type: FUNC test + * @tc.require: issueI#I8KE59 + */ + it('activate_permission_test_001', 0, async function (done) { + console.info(`activate_permission_test_001 start`); + let policyInfo = GetRealPolicyInfo(); + try { + fileShare.activatePermission(policyInfo).then((result) => { + console.info(`activatePermissionTest001 success`); + expect(result.length).assertEqual(0); + done(); + }).catch((err) => { + console.info(`activatePermissionTest001 err : ${err.code}`); + expect(err.code).assertEqual(DEVICE_TYPE); + done(); + }) + } catch (error) { + expect(error.code).assertEqual(DEVICE_TYPE); + done(); + } + console.info(`activate_permission_test_001 end`); + }) + + /** + * @tc.name: activate_permission_test_002 + * @tc.desc: Enable the Inexistent URI that have been permanently authorized test + * @tc.type: FUNC test + * @tc.require: issueI#I8KE59 + */ + it('activate_permission_test_002', 0, async function (done) { + console.info(`activate_permission_test_002 start`); + let policyInfo = GetInexistentPolicyInfo(); + try { + fileShare.activatePermission(policyInfo).then(() => { + console.info(`activatePermissionTest002 Fail`); + expect().assertFail(); + done(); + }).catch((err) => { + console.info(`activatePermissionTest002 success : ${err.code}`); + expect(err.code).assertEqual(DEVICE_TYPE); + done(); + }) + } catch (error) { + expect(error.code).assertEqual(DEVICE_TYPE); + done(); + } + console.info(`activate_permission_test_002 end`); + }) + + /** + * @tc.name: deactivate_permission_test_001 + * @tc.desc: Stop the authorized real URI that has been enabled test + * @tc.type: FUNC test + * @tc.require: issueI#I8KE59 + */ + it('deactivate_permission_test_001', 0, async function (done) { + console.info(`deactivate_permission_test_001 start`); + let policyInfo = GetRealPolicyInfo(); + console.info(`GetRealPolicyInfo`); + try { + console.info(`fileShare.deactivatePermission`); + console.info(`fileShare.deactivatePermission`); + fileShare.deactivatePermission(policyInfo).then((result) => { + console.info(`deactivatePermissionTest001 success`); + expect(result.length).assertEqual(0); + done(); + }).catch((err) => { + console.info(`deactivatePermissionTest001 err : ${err.code}`); + expect(err.code).assertEqual(DEVICE_TYPE); + done(); + }) + } catch (error) { + console.info(`deactivatePermissionTest001 error : ${error.code}`); + expect(error.code).assertEqual(DEVICE_TYPE); + done(); + } + console.info(`deactivate_permission_test_001 end`); + }) + + /** + * @tc.name: deactivate_permission_test_002 + * @tc.desc: Stop the authorized Inexistent URI that has been enabled test + * @tc.type: FUNC test + * @tc.require: issueI#I8KE59 + */ + it('deactivate_permission_test_002', 0, async function (done) { + console.info(`deactivate_permission_test_002 start`); + let policyInfo = GetInexistentPolicyInfo(); + try { + fileShare.deactivatePermission(policyInfo).then(() => { + console.info(`deactivatePermissionTest002 Fail`); + expect().assertFail(); + done(); + }).catch((err) => { + console.info(`deactivatePermissionTest002 success : ${err.code}`); + expect(err.code).assertEqual(DEVICE_TYPE); + done(); + }) + } catch (error) { + expect(error.code).assertEqual(DEVICE_TYPE); + done(); + } + console.info(`deactivate_permission_test_002 end`); + }) +}); \ No newline at end of file diff --git a/interfaces/test/unittest/src/file_share/config.json b/interfaces/test/unittest/src/file_share/config.json new file mode 100644 index 000000000..402a8e7a1 --- /dev/null +++ b/interfaces/test/unittest/src/file_share/config.json @@ -0,0 +1,76 @@ +{ + "app": { + "bundleName": "ohos.file.share.test", + "vendor": "acts", + "version": { + "code": 1000000, + "name": "1.0.0" + }, + "apiVersion": { + "compatible": 8, + "target": 9 + } + }, + "deviceConfig": {}, + "module": { + "package": "ohos.file.share.test", + "name": ".MainAbility", + "deviceType": [ + "default", + "tablet", + "2in1" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "entry", + "moduleType": "entry" + }, + "reqPermissions": [ + { + "name": "ohos.permission.FILE_ACCESS_PERSIST" + }, + { + "name": "ohos.permission.SET_SANDBOX_POLICY" + }, + { + "name": "ohos.permission.GET_BUNDLE_INFO" + }, + { + "name": "ohos.permission.GET_BUNDLE_INFO_PRIVILEGED" + } + ], + "abilities": [ + { + "visible": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ], + "name": "ohos.file.share.test.MainAbility", + "icon": "$media:icon", + "description": "$string:mainability_description", + "label": "$string:app_name", + "type": "page", + "launchType": "standard" + } + ], + "js": [ + { + "pages": [ + "pages/index/index" + ], + "name": "default", + "window": { + "designWidth": 720, + "autoDesignWidth": false + } + } + ] + } +} diff --git a/interfaces/test/unittest/src/file_share/openharmony_sx.p7b b/interfaces/test/unittest/src/file_share/openharmony_sx.p7b new file mode 100644 index 0000000000000000000000000000000000000000..abc19905093bfd071e8360b090d77434c01d9130 GIT binary patch literal 3585 zcmcgv3v?9K8O}ZujIa<55~M=H@(49-a%UgOZY=7|>@&&iX7-ic7`x8y&c1hMGCTVU z<x0Tr+XG}0dNQ3b_{M@3GLf)^JyxpIPG6v1eUW262w zCk){7j4*&s`Jxm@hFL;D`H|^#G|1x`pltH%bfiKP;(yeQnxI0b0a^v2BLSpRhe9Nz z71kjV(O{Iz%bVxRDVkIG$q>!*^FkV(THB~mYP55UI1%DVmWLa)I(3tZhZoG14`;xV z36e;W{AB(#6-5fG`GRDIOwbI;DvBA|7?Ls*Y?7w(eC(*b1W1;PhN1z26Skxf zK8V3&vDTmlyVGK{ps>?WM7t==VnK~D6ootUVK{3+;IO3yZgHhmElC`Yv>Gv(Hlb*@ z$$_bL2#o156P81J;1(oobtACX*+s!|s|~Z`Swq;XcjI`gA73%6_W7BBauK7%J`{xxQlX|+!4Ri5W`SIF$&IM#MM!_2g&+PiI~@8 zXZ%Wa7zc)|ITEm9F)YXLA_n(hPCReZu4f|rmbcT<-&SgF{aHzB#dmnC0=KZ z*^n*@LbWgj+UbN9pcv|E29 z@z6oDD&EvZbpWcR&O(NiNq2`iJa1kXRiwMYs=r1HTyt_eAp<@J@R5gSFn1crOn`jQPkjY_}UG2hsELKbGbUo zVQ~5!uvL%PJic~Yn+5e=Bgn)DAug+)pU`48+WvQhuib8O^Xr=o`r;<|4WtDrlQZ#X zQ94AO6B%v8#y5N#jP8sQf873i!AOB)&P#8?B zk|`me1WHKBKQ*0IGPMp;s-R&LFh4K}0P2U5sQtbq+F4qlQ;mH{+JP5TmW?UC2C-=P zeMm%OBuI(0;~nwe_YCa(6z<-8%bUNP^Tc*z(-PV0YSG`e-~QS|A0Ih34cprEwdB1o z_EF+G=Uvq=z0r?9T0ZHIkEstzsO9@6Ke3T-W>C$iGj)b^GMKb@;zzxM@<|_zUEex* zbk_VmUz9G`pfG{)Lcj!(=s!}aL_B6Om@3%grIVx+-y%-FxTVK>=$&8hy8m?P#czBv zk$rT{B+(KO5%d*)=?NeSBp<7SR+_h0t zk$dV=YT=2`ChX$*qv!ea<}iOI9MdizoePNkz|zeDvxiDo{e7jg(=>M#i6Awk9YLa^ zc^xy!fQl>RDXsgtv_vlPmq@Y3qxnbUvL$QI$XfPYnnQmcfRnBB_bA4$$-H>V>wWj0 zCpX+(`1bkty;9S1%(R+Wg>2r|e|C@Wf^%Tp_@jXbAO5rHri~BCK7L{Ih6}!1F3hf8 z&K|A#@l-tJwJdBJWBTVt1l$cK2t=tAh*JtA{9`neL}eqGRMlLu{+U~UcA~_#?cLS2 zJ6CsR$G<(gdXjJn2#P_rGg}M2eJ;E8&d2)>dv-s(qjuoN74M9Dp?S}=2NvJ1>FL_M z{x-$hL$A})8BuR7_oNuid}ih^=zF)#7-OHfX~o(8hd|3+dHuiAh7-)DOy8Qb;PGzT zqxr*Ist(l+95hYtE#-Mc@H{#(%%f@K%kxHOaviHTLX04dN;#58f_Z%u@p~vVeeLJ_ z`<|`RY+rU@lkcx5;aRoP<5Sjs^**-*-LbXoo9$<6r@VFeG1>25Xxg-p@hsiH@JGzf z%)s-1vV)G@A3i>(vHiKhXJ37C%9_nzEnQmi)w`>wFFn8LzTI`z=TE(PY~{BK*Tw)K zMv2okIr4vS?3&^>>V%O*N&qPa$s<42F8K|2X3=lYH%EL=?QdLrd~e16!d0$Re-uob)v8X=aK$TyQRVeYEz!Zxf0E literal 0 HcmV?d00001 -- Gitee