Merge branch 'develop-6crm' into feature-NEWDWH2021-720
This commit is contained in:
commit
714c14bb12
5
.gitignore
vendored
5
.gitignore
vendored
@ -8,3 +8,8 @@ __pycache__/
|
||||
|
||||
# StepFunctionsステートメント定義変換後のフォルダ
|
||||
stepfunctions/*/build
|
||||
**/.vscode/settings.json
|
||||
|
||||
# python test
|
||||
.coverage
|
||||
.report/
|
||||
|
||||
25
.vscode/launch.json
vendored
Normal file
25
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
// IntelliSense を使用して利用可能な属性を学べます。
|
||||
// 既存の属性の説明をホバーして表示します。
|
||||
// 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: データ取り込みローカル実行",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
// windowsだと\区切りかも
|
||||
"program": "ecs\\dataimport\\dataimport\\controller.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true,
|
||||
"envFile": "${workspaceFolder}/.env"
|
||||
},
|
||||
{
|
||||
"name": "Python: Attach using Process Id",
|
||||
"type": "python",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}",
|
||||
"justMyCode": true
|
||||
}
|
||||
]
|
||||
}
|
||||
13
ecs/Dockerfile/Pipfile
Normal file
13
ecs/Dockerfile/Pipfile
Normal file
@ -0,0 +1,13 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
boto3 = "*"
|
||||
pymysql = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[requires]
|
||||
python_version = "3.9"
|
||||
85
ecs/Dockerfile/Pipfile.lock
generated
Normal file
85
ecs/Dockerfile/Pipfile.lock
generated
Normal file
@ -0,0 +1,85 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "7c69c0c237f231fcd67984f1d1b171c2eebfe00ed1877894bbbe77e201862057"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.9"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:490f5e88f5551b33ae3019a37412158b76426d63d1fb910968ade9b6a024e5fe",
|
||||
"sha256:e284705da36faa668c715ae1f74ebbff4320dbfbe3a733df3a8ab076d1ed1226"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.24.14"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:bb56fa77b8fa1ec367c2e16dee62d60000451aac5140dcce3ebddc167fd5c593",
|
||||
"sha256:df1e9b208ff93daac7c645b0b04fb6dccd7f20262eae24d87941727025cbeece"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.27.14"
|
||||
},
|
||||
"jmespath": {
|
||||
"hashes": [
|
||||
"sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980",
|
||||
"sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"pymysql": {
|
||||
"hashes": [
|
||||
"sha256:41fc3a0c5013d5f039639442321185532e3e2c8924687abe6537de157d403641",
|
||||
"sha256:816927a350f38d56072aeca5dfb10221fe1dc653745853d30a216637f5d7ad36"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
|
||||
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
|
||||
"version": "==2.8.2"
|
||||
},
|
||||
"s3transfer": {
|
||||
"hashes": [
|
||||
"sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd",
|
||||
"sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14",
|
||||
"sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||
"version": "==1.26.9"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
||||
11
ecs/crm-datafetch/.dockerignore
Normal file
11
ecs/crm-datafetch/.dockerignore
Normal file
@ -0,0 +1,11 @@
|
||||
tests/*
|
||||
.coverage
|
||||
.env
|
||||
.env.example
|
||||
.report/*
|
||||
.vscode/*
|
||||
.pytest_cache/*
|
||||
*/__pychache__/*
|
||||
Dockerfile
|
||||
pytest.ini
|
||||
README.md
|
||||
17
ecs/crm-datafetch/.env.example
Normal file
17
ecs/crm-datafetch/.env.example
Normal file
@ -0,0 +1,17 @@
|
||||
CRM_AUTH_DOMAIN=test
|
||||
CRM_USER_NAME=test
|
||||
CRM_USER_PASSWORD=test
|
||||
CRM_USER_SECURITY_TOKEN=test
|
||||
CRM_CONFIG_BUCKET=test
|
||||
CRM_BACKUP_BUCKET=test
|
||||
IMPORT_DATA_BUCKET=test
|
||||
OBJECT_INFO_FOLDER=test
|
||||
OBJECT_INFO_FILENAME=test
|
||||
PROCESS_RESULT_FOLDER=test
|
||||
PROCESS_RESULT_FILENAME=test
|
||||
LAST_FETCH_DATE_FOLDER=test
|
||||
CRM_IMPORT_DATA_FOLDER=test
|
||||
LAST_FETCH_DATE_BACKUP_FOLDER=test
|
||||
RESPONSE_JSON_BACKUP_FOLDER=test
|
||||
CRM_IMPORT_DATA_BACKUP_FOLDER=test
|
||||
LOG_LEVEL=INFO
|
||||
16
ecs/crm-datafetch/.vscode/launch.json
vendored
Normal file
16
ecs/crm-datafetch/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: Current File",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
// エントリーポイントのファイルに変更すること
|
||||
"program": "main.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true,
|
||||
// 環境変数が必要な場合に読み込む環境変数ファイル
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
}
|
||||
]
|
||||
}
|
||||
17
ecs/crm-datafetch/.vscode/python.code-snippets
vendored
Normal file
17
ecs/crm-datafetch/.vscode/python.code-snippets
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"Generate Test docstring": {
|
||||
"scope": "python",
|
||||
"prefix": "\"\"\"\"\"\"",
|
||||
"body": [
|
||||
"\"\"\"",
|
||||
"Cases:",
|
||||
" $1",
|
||||
"Arranges:",
|
||||
" $2",
|
||||
"Expects:",
|
||||
" $3",
|
||||
"\"\"\""
|
||||
],
|
||||
"description": "Test docstring (User Snippets)"
|
||||
}
|
||||
}
|
||||
22
ecs/crm-datafetch/.vscode/recommend_settings.json
vendored
Normal file
22
ecs/crm-datafetch/.vscode/recommend_settings.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": null,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
"python.linting.lintOnSave": true,
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.pylintEnabled": false,
|
||||
"python.linting.flake8Enabled": true,
|
||||
"python.linting.flake8Args": ["--max-line-length=150", "--ignore=F541"],
|
||||
"python.formatting.provider": "autopep8",
|
||||
"python.formatting.autopep8Args": ["--max-line-length", "150"],
|
||||
"python.testing.pytestArgs": [
|
||||
"tests"
|
||||
// "--walk-through" // 一気通貫テストを行いたい場合はコメントを外す
|
||||
],
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": true
|
||||
}
|
||||
19
ecs/crm-datafetch/Dockerfile
Normal file
19
ecs/crm-datafetch/Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
FROM python:3.8
|
||||
|
||||
ENV TZ="Asia/Tokyo"
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY Pipfile Pipfile.lock ./
|
||||
RUN \
|
||||
apt update -y && \
|
||||
# パッケージのセキュリティアップデートのみを適用するコマンド
|
||||
apt install -y unattended-upgrades && \
|
||||
unattended-upgrades && \
|
||||
pip install pipenv --no-cache-dir && \
|
||||
pipenv install --system --deploy && \
|
||||
pip uninstall -y pipenv virtualenv-clone virtualenv
|
||||
|
||||
COPY main.py ./
|
||||
COPY src ./src
|
||||
|
||||
CMD [ "python", "./main.py" ]
|
||||
26
ecs/crm-datafetch/Pipfile
Normal file
26
ecs/crm-datafetch/Pipfile
Normal file
@ -0,0 +1,26 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[scripts]
|
||||
test = "pytest tests/"
|
||||
"test:cov" = "pytest --cov=src --cov-branch --cov-report=term-missing tests/"
|
||||
"test:report" = "pytest --cov=src --cov-branch --cov-report=term-missing --html=.report/unit_test/test_result.html tests/"
|
||||
"test:walk-through" = "pytest tests/test_walk_through.py --walk-through --cov-report=term-missing --html=.report/walk_through/test_result.html"
|
||||
|
||||
[packages]
|
||||
boto3 = "*"
|
||||
simple-salesforce = "*"
|
||||
tenacity = "*"
|
||||
|
||||
[dev-packages]
|
||||
autopep8 = "*"
|
||||
flake8 = "*"
|
||||
pytest = "*"
|
||||
pytest-cov = "*"
|
||||
pytest-html = "*"
|
||||
moto = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.8"
|
||||
842
ecs/crm-datafetch/Pipfile.lock
generated
Normal file
842
ecs/crm-datafetch/Pipfile.lock
generated
Normal file
@ -0,0 +1,842 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "7006de596d6123ecd56760b584ab75430fa6bcfc0ecd3fdf49f08368ff53477d"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.8"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6",
|
||||
"sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==22.1.0"
|
||||
},
|
||||
"authlib": {
|
||||
"hashes": [
|
||||
"sha256:1286e2d5ef5bfe5a11cc2d0a0d1031f0393f6ce4d61f5121cfe87fa0054e98bd",
|
||||
"sha256:6e74a4846ac36dfc882b3cc2fbd3d9eb410a627f2f2dc11771276655345223b1"
|
||||
],
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:aec404d06690a0ca806592efc6bbe30a9ac88fa2ad73b6d907f7794a8b3b1459",
|
||||
"sha256:df3d6ef02304bd7c3711090936c092d5db735dda109decac1e236c3ef7fdb7af"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.24.42"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:38a180a6666c5a9b069a75ec3cf374ff2a64c3e90c9f24a916858bcdeb04456d",
|
||||
"sha256:f8e6c2f69a9d577fb9c69e4e74f49f4315a48decee0e7dc88b6e470772110884"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.27.42"
|
||||
},
|
||||
"cached-property": {
|
||||
"hashes": [
|
||||
"sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130",
|
||||
"sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"
|
||||
],
|
||||
"version": "==1.5.2"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d",
|
||||
"sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2022.6.15"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
"sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5",
|
||||
"sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef",
|
||||
"sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104",
|
||||
"sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426",
|
||||
"sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405",
|
||||
"sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375",
|
||||
"sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a",
|
||||
"sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e",
|
||||
"sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc",
|
||||
"sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf",
|
||||
"sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185",
|
||||
"sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497",
|
||||
"sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3",
|
||||
"sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35",
|
||||
"sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c",
|
||||
"sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83",
|
||||
"sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21",
|
||||
"sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca",
|
||||
"sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984",
|
||||
"sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac",
|
||||
"sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd",
|
||||
"sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee",
|
||||
"sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a",
|
||||
"sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2",
|
||||
"sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192",
|
||||
"sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7",
|
||||
"sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585",
|
||||
"sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f",
|
||||
"sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e",
|
||||
"sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27",
|
||||
"sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b",
|
||||
"sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e",
|
||||
"sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e",
|
||||
"sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d",
|
||||
"sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c",
|
||||
"sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415",
|
||||
"sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82",
|
||||
"sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02",
|
||||
"sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314",
|
||||
"sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325",
|
||||
"sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c",
|
||||
"sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3",
|
||||
"sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914",
|
||||
"sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045",
|
||||
"sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d",
|
||||
"sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9",
|
||||
"sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5",
|
||||
"sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2",
|
||||
"sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c",
|
||||
"sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3",
|
||||
"sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2",
|
||||
"sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8",
|
||||
"sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d",
|
||||
"sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d",
|
||||
"sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9",
|
||||
"sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162",
|
||||
"sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76",
|
||||
"sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4",
|
||||
"sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e",
|
||||
"sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9",
|
||||
"sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6",
|
||||
"sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b",
|
||||
"sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01",
|
||||
"sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"
|
||||
],
|
||||
"version": "==1.15.1"
|
||||
},
|
||||
"charset-normalizer": {
|
||||
"hashes": [
|
||||
"sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5",
|
||||
"sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.1.0"
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59",
|
||||
"sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596",
|
||||
"sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3",
|
||||
"sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5",
|
||||
"sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab",
|
||||
"sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884",
|
||||
"sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82",
|
||||
"sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b",
|
||||
"sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441",
|
||||
"sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa",
|
||||
"sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d",
|
||||
"sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b",
|
||||
"sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a",
|
||||
"sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6",
|
||||
"sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157",
|
||||
"sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280",
|
||||
"sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282",
|
||||
"sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67",
|
||||
"sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8",
|
||||
"sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046",
|
||||
"sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327",
|
||||
"sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==37.0.4"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
|
||||
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==3.3"
|
||||
},
|
||||
"isodate": {
|
||||
"hashes": [
|
||||
"sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96",
|
||||
"sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"jmespath": {
|
||||
"hashes": [
|
||||
"sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980",
|
||||
"sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"lxml": {
|
||||
"hashes": [
|
||||
"sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318",
|
||||
"sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c",
|
||||
"sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b",
|
||||
"sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000",
|
||||
"sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73",
|
||||
"sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d",
|
||||
"sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb",
|
||||
"sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8",
|
||||
"sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2",
|
||||
"sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345",
|
||||
"sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94",
|
||||
"sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e",
|
||||
"sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b",
|
||||
"sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc",
|
||||
"sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a",
|
||||
"sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9",
|
||||
"sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc",
|
||||
"sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387",
|
||||
"sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb",
|
||||
"sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7",
|
||||
"sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4",
|
||||
"sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97",
|
||||
"sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67",
|
||||
"sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627",
|
||||
"sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7",
|
||||
"sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd",
|
||||
"sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3",
|
||||
"sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7",
|
||||
"sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130",
|
||||
"sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b",
|
||||
"sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036",
|
||||
"sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785",
|
||||
"sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca",
|
||||
"sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91",
|
||||
"sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc",
|
||||
"sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536",
|
||||
"sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391",
|
||||
"sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3",
|
||||
"sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d",
|
||||
"sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21",
|
||||
"sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3",
|
||||
"sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d",
|
||||
"sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29",
|
||||
"sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715",
|
||||
"sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed",
|
||||
"sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25",
|
||||
"sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c",
|
||||
"sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785",
|
||||
"sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837",
|
||||
"sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4",
|
||||
"sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b",
|
||||
"sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2",
|
||||
"sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067",
|
||||
"sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448",
|
||||
"sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d",
|
||||
"sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2",
|
||||
"sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc",
|
||||
"sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c",
|
||||
"sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5",
|
||||
"sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84",
|
||||
"sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8",
|
||||
"sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf",
|
||||
"sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7",
|
||||
"sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e",
|
||||
"sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb",
|
||||
"sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b",
|
||||
"sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3",
|
||||
"sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad",
|
||||
"sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8",
|
||||
"sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==4.9.1"
|
||||
},
|
||||
"platformdirs": {
|
||||
"hashes": [
|
||||
"sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788",
|
||||
"sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.5.2"
|
||||
},
|
||||
"pycparser": {
|
||||
"hashes": [
|
||||
"sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
|
||||
"sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"
|
||||
],
|
||||
"version": "==2.21"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
|
||||
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.8.2"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7",
|
||||
"sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"
|
||||
],
|
||||
"version": "==2022.1"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983",
|
||||
"sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"
|
||||
],
|
||||
"markers": "python_version >= '3.7' and python_version < '4'",
|
||||
"version": "==2.28.1"
|
||||
},
|
||||
"requests-file": {
|
||||
"hashes": [
|
||||
"sha256:07d74208d3389d01c38ab89ef403af0cfec63957d53a0081d8eca738d0247d8e",
|
||||
"sha256:dfe5dae75c12481f68ba353183c53a65e6044c923e64c24b2209f6c7570ca953"
|
||||
],
|
||||
"version": "==1.5.1"
|
||||
},
|
||||
"requests-toolbelt": {
|
||||
"hashes": [
|
||||
"sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f",
|
||||
"sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"
|
||||
],
|
||||
"version": "==0.9.1"
|
||||
},
|
||||
"s3transfer": {
|
||||
"hashes": [
|
||||
"sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd",
|
||||
"sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"simple-salesforce": {
|
||||
"hashes": [
|
||||
"sha256:15d6943e52252c9cc28e1779803354f2a36c88b72056499e07eb06cd652f149c",
|
||||
"sha256:7931038081c445e9459ddc014aaf7f540b1131a31596956cb5d7c0e7b7e0c4cb"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.12.1"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"tenacity": {
|
||||
"hashes": [
|
||||
"sha256:43242a20e3e73291a28bcbcacfd6e000b02d3857a9a9fff56b297a27afdc932f",
|
||||
"sha256:f78f4ea81b0fabc06728c11dc2a8c01277bfc5181b321a4770471902e3eb844a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==8.0.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc",
|
||||
"sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'",
|
||||
"version": "==1.26.11"
|
||||
},
|
||||
"zeep": {
|
||||
"hashes": [
|
||||
"sha256:5867f2eadd6b028d9751f4155af590d3aaf9280e3a0ed5e15a53343921c956e5",
|
||||
"sha256:81c491092b71f5b276de8c63dfd452be3f322622c48a54f3a497cf913bdfb2f4"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.1.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6",
|
||||
"sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==22.1.0"
|
||||
},
|
||||
"autopep8": {
|
||||
"hashes": [
|
||||
"sha256:44f0932855039d2c15c4510d6df665e4730f2b8582704fa48f9c55bd3e17d979",
|
||||
"sha256:ed77137193bbac52d029a52c59bec1b0629b5a186c495f1eb21b126ac466083f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.6.0"
|
||||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:aec404d06690a0ca806592efc6bbe30a9ac88fa2ad73b6d907f7794a8b3b1459",
|
||||
"sha256:df3d6ef02304bd7c3711090936c092d5db735dda109decac1e236c3ef7fdb7af"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.24.42"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:38a180a6666c5a9b069a75ec3cf374ff2a64c3e90c9f24a916858bcdeb04456d",
|
||||
"sha256:f8e6c2f69a9d577fb9c69e4e74f49f4315a48decee0e7dc88b6e470772110884"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.27.42"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d",
|
||||
"sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2022.6.15"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
"sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5",
|
||||
"sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef",
|
||||
"sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104",
|
||||
"sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426",
|
||||
"sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405",
|
||||
"sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375",
|
||||
"sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a",
|
||||
"sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e",
|
||||
"sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc",
|
||||
"sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf",
|
||||
"sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185",
|
||||
"sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497",
|
||||
"sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3",
|
||||
"sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35",
|
||||
"sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c",
|
||||
"sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83",
|
||||
"sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21",
|
||||
"sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca",
|
||||
"sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984",
|
||||
"sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac",
|
||||
"sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd",
|
||||
"sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee",
|
||||
"sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a",
|
||||
"sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2",
|
||||
"sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192",
|
||||
"sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7",
|
||||
"sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585",
|
||||
"sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f",
|
||||
"sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e",
|
||||
"sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27",
|
||||
"sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b",
|
||||
"sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e",
|
||||
"sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e",
|
||||
"sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d",
|
||||
"sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c",
|
||||
"sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415",
|
||||
"sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82",
|
||||
"sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02",
|
||||
"sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314",
|
||||
"sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325",
|
||||
"sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c",
|
||||
"sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3",
|
||||
"sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914",
|
||||
"sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045",
|
||||
"sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d",
|
||||
"sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9",
|
||||
"sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5",
|
||||
"sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2",
|
||||
"sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c",
|
||||
"sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3",
|
||||
"sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2",
|
||||
"sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8",
|
||||
"sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d",
|
||||
"sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d",
|
||||
"sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9",
|
||||
"sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162",
|
||||
"sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76",
|
||||
"sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4",
|
||||
"sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e",
|
||||
"sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9",
|
||||
"sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6",
|
||||
"sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b",
|
||||
"sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01",
|
||||
"sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"
|
||||
],
|
||||
"version": "==1.15.1"
|
||||
},
|
||||
"charset-normalizer": {
|
||||
"hashes": [
|
||||
"sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5",
|
||||
"sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.1.0"
|
||||
},
|
||||
"coverage": {
|
||||
"extras": [
|
||||
"toml"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:0895ea6e6f7f9939166cc835df8fa4599e2d9b759b02d1521b574e13b859ac32",
|
||||
"sha256:0f211df2cba951ffcae210ee00e54921ab42e2b64e0bf2c0befc977377fb09b7",
|
||||
"sha256:147605e1702d996279bb3cc3b164f408698850011210d133a2cb96a73a2f7996",
|
||||
"sha256:24b04d305ea172ccb21bee5bacd559383cba2c6fcdef85b7701cf2de4188aa55",
|
||||
"sha256:25b7ec944f114f70803d6529394b64f8749e93cbfac0fe6c5ea1b7e6c14e8a46",
|
||||
"sha256:2b20286c2b726f94e766e86a3fddb7b7e37af5d0c635bdfa7e4399bc523563de",
|
||||
"sha256:2dff52b3e7f76ada36f82124703f4953186d9029d00d6287f17c68a75e2e6039",
|
||||
"sha256:2f8553878a24b00d5ab04b7a92a2af50409247ca5c4b7a2bf4eabe94ed20d3ee",
|
||||
"sha256:3def6791adf580d66f025223078dc84c64696a26f174131059ce8e91452584e1",
|
||||
"sha256:422fa44070b42fef9fb8dabd5af03861708cdd6deb69463adc2130b7bf81332f",
|
||||
"sha256:4f89d8e03c8a3757aae65570d14033e8edf192ee9298303db15955cadcff0c63",
|
||||
"sha256:5336e0352c0b12c7e72727d50ff02557005f79a0b8dcad9219c7c4940a930083",
|
||||
"sha256:54d8d0e073a7f238f0666d3c7c0d37469b2aa43311e4024c925ee14f5d5a1cbe",
|
||||
"sha256:5ef42e1db047ca42827a85e34abe973971c635f83aed49611b7f3ab49d0130f0",
|
||||
"sha256:5f65e5d3ff2d895dab76b1faca4586b970a99b5d4b24e9aafffc0ce94a6022d6",
|
||||
"sha256:6c3ccfe89c36f3e5b9837b9ee507472310164f352c9fe332120b764c9d60adbe",
|
||||
"sha256:6d0b48aff8e9720bdec315d67723f0babd936a7211dc5df453ddf76f89c59933",
|
||||
"sha256:6fe75dcfcb889b6800f072f2af5a331342d63d0c1b3d2bf0f7b4f6c353e8c9c0",
|
||||
"sha256:79419370d6a637cb18553ecb25228893966bd7935a9120fa454e7076f13b627c",
|
||||
"sha256:7bb00521ab4f99fdce2d5c05a91bddc0280f0afaee0e0a00425e28e209d4af07",
|
||||
"sha256:80db4a47a199c4563d4a25919ff29c97c87569130375beca3483b41ad5f698e8",
|
||||
"sha256:866ebf42b4c5dbafd64455b0a1cd5aa7b4837a894809413b930026c91e18090b",
|
||||
"sha256:8af6c26ba8df6338e57bedbf916d76bdae6308e57fc8f14397f03b5da8622b4e",
|
||||
"sha256:a13772c19619118903d65a91f1d5fea84be494d12fd406d06c849b00d31bf120",
|
||||
"sha256:a697977157adc052284a7160569b36a8bbec09db3c3220642e6323b47cec090f",
|
||||
"sha256:a9032f9b7d38bdf882ac9f66ebde3afb8145f0d4c24b2e600bc4c6304aafb87e",
|
||||
"sha256:b5e28db9199dd3833cc8a07fa6cf429a01227b5d429facb56eccd765050c26cd",
|
||||
"sha256:c77943ef768276b61c96a3eb854eba55633c7a3fddf0a79f82805f232326d33f",
|
||||
"sha256:d230d333b0be8042ac34808ad722eabba30036232e7a6fb3e317c49f61c93386",
|
||||
"sha256:d4548be38a1c810d79e097a38107b6bf2ff42151900e47d49635be69943763d8",
|
||||
"sha256:d4e7ced84a11c10160c0697a6cc0b214a5d7ab21dfec1cd46e89fbf77cc66fae",
|
||||
"sha256:d56f105592188ce7a797b2bd94b4a8cb2e36d5d9b0d8a1d2060ff2a71e6b9bbc",
|
||||
"sha256:d714af0bdba67739598849c9f18efdcc5a0412f4993914a0ec5ce0f1e864d783",
|
||||
"sha256:d774d9e97007b018a651eadc1b3970ed20237395527e22cbeb743d8e73e0563d",
|
||||
"sha256:e0524adb49c716ca763dbc1d27bedce36b14f33e6b8af6dba56886476b42957c",
|
||||
"sha256:e2618cb2cf5a7cc8d698306e42ebcacd02fb7ef8cfc18485c59394152c70be97",
|
||||
"sha256:e36750fbbc422c1c46c9d13b937ab437138b998fe74a635ec88989afb57a3978",
|
||||
"sha256:edfdabe7aa4f97ed2b9dd5dde52d2bb29cb466993bb9d612ddd10d0085a683cf",
|
||||
"sha256:f22325010d8824594820d6ce84fa830838f581a7fd86a9235f0d2ed6deb61e29",
|
||||
"sha256:f23876b018dfa5d3e98e96f5644b109090f16a4acb22064e0f06933663005d39",
|
||||
"sha256:f7bd0ffbcd03dc39490a1f40b2669cc414fae0c4e16b77bb26806a4d0b7d1452"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==6.4.2"
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59",
|
||||
"sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596",
|
||||
"sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3",
|
||||
"sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5",
|
||||
"sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab",
|
||||
"sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884",
|
||||
"sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82",
|
||||
"sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b",
|
||||
"sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441",
|
||||
"sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa",
|
||||
"sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d",
|
||||
"sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b",
|
||||
"sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a",
|
||||
"sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6",
|
||||
"sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157",
|
||||
"sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280",
|
||||
"sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282",
|
||||
"sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67",
|
||||
"sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8",
|
||||
"sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046",
|
||||
"sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327",
|
||||
"sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==37.0.4"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:44e3ecd719bba1cb2ae65d1b54212cc9df4f5db15ac271f8856e5e6c2eebefed",
|
||||
"sha256:9c51d3d1426379fd444d3b79eabbeb887849368bd053039066439523d8486961"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.0.1"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
|
||||
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==3.3"
|
||||
},
|
||||
"iniconfig": {
|
||||
"hashes": [
|
||||
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
|
||||
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
|
||||
],
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852",
|
||||
"sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==3.1.2"
|
||||
},
|
||||
"jmespath": {
|
||||
"hashes": [
|
||||
"sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980",
|
||||
"sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003",
|
||||
"sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88",
|
||||
"sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5",
|
||||
"sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7",
|
||||
"sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a",
|
||||
"sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603",
|
||||
"sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1",
|
||||
"sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135",
|
||||
"sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247",
|
||||
"sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6",
|
||||
"sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601",
|
||||
"sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77",
|
||||
"sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02",
|
||||
"sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e",
|
||||
"sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63",
|
||||
"sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f",
|
||||
"sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980",
|
||||
"sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b",
|
||||
"sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812",
|
||||
"sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff",
|
||||
"sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96",
|
||||
"sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1",
|
||||
"sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925",
|
||||
"sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a",
|
||||
"sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6",
|
||||
"sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e",
|
||||
"sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f",
|
||||
"sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4",
|
||||
"sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f",
|
||||
"sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3",
|
||||
"sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c",
|
||||
"sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a",
|
||||
"sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417",
|
||||
"sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a",
|
||||
"sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a",
|
||||
"sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37",
|
||||
"sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452",
|
||||
"sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933",
|
||||
"sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a",
|
||||
"sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.1.1"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
|
||||
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.7.0"
|
||||
},
|
||||
"moto": {
|
||||
"hashes": [
|
||||
"sha256:8bb8e267d9b948509d4739d81d995615a193d2c459f5c0a979aaeb0d3bd4b381",
|
||||
"sha256:cbe8ad8a949f519771e5d25b670738604757fb67cd474d75d14c20677582e81f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.1.16"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
|
||||
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==21.3"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
|
||||
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719",
|
||||
"sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==1.11.0"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:289cdc0969d589d90752582bef6dff57c5fbc6949ee8b013ad6d6449a8ae9437",
|
||||
"sha256:beaba44501f89d785be791c9462553f06958a221d166c64e1f107320f839acc2"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.9.0"
|
||||
},
|
||||
"pycparser": {
|
||||
"hashes": [
|
||||
"sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
|
||||
"sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"
|
||||
],
|
||||
"version": "==2.21"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2",
|
||||
"sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.5.0"
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb",
|
||||
"sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.8'",
|
||||
"version": "==3.0.9"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c",
|
||||
"sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==7.1.2"
|
||||
},
|
||||
"pytest-cov": {
|
||||
"hashes": [
|
||||
"sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6",
|
||||
"sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"pytest-html": {
|
||||
"hashes": [
|
||||
"sha256:3ee1cf319c913d19fe53aeb0bc400e7b0bc2dbeb477553733db1dad12eb75ee3",
|
||||
"sha256:b7f82f123936a3f4d2950bc993c2c1ca09ce262c9ae12f9ac763a2401380b455"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.1.1"
|
||||
},
|
||||
"pytest-metadata": {
|
||||
"hashes": [
|
||||
"sha256:39261ee0086f17649b180baf2a8633e1922a4c4b6fcc28a2de7d8127a82541bf",
|
||||
"sha256:fcd2f416f15be295943527b3c8ba16a44ae5a7141939c90c3dc5ce9d167cf2a5"
|
||||
],
|
||||
"markers": "python_version >= '3.7' and python_version < '4'",
|
||||
"version": "==2.0.2"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
|
||||
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.8.2"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7",
|
||||
"sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"
|
||||
],
|
||||
"version": "==2022.1"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983",
|
||||
"sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"
|
||||
],
|
||||
"markers": "python_version >= '3.7' and python_version < '4'",
|
||||
"version": "==2.28.1"
|
||||
},
|
||||
"responses": {
|
||||
"hashes": [
|
||||
"sha256:2dcc863ba63963c0c3d9ee3fa9507cbe36b7d7b0fccb4f0bdfd9e96c539b1487",
|
||||
"sha256:b82502eb5f09a0289d8e209e7bad71ef3978334f56d09b444253d5ad67bf5253"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==0.21.0"
|
||||
},
|
||||
"s3transfer": {
|
||||
"hashes": [
|
||||
"sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd",
|
||||
"sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.2"
|
||||
},
|
||||
"tomli": {
|
||||
"hashes": [
|
||||
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
|
||||
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc",
|
||||
"sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'",
|
||||
"version": "==1.26.11"
|
||||
},
|
||||
"werkzeug": {
|
||||
"hashes": [
|
||||
"sha256:4d7013ef96fd197d1cdeb03e066c6c5a491ccb44758a5b2b91137319383e5a5a",
|
||||
"sha256:7e1db6a5ba6b9a8be061e47e900456355b8714c0f238b0313f53afce1a55a79a"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.2.1"
|
||||
},
|
||||
"xmltodict": {
|
||||
"hashes": [
|
||||
"sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56",
|
||||
"sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"
|
||||
],
|
||||
"markers": "python_version >= '3.4'",
|
||||
"version": "==0.13.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
208
ecs/crm-datafetch/README.md
Normal file
208
ecs/crm-datafetch/README.md
Normal file
@ -0,0 +1,208 @@
|
||||
# CRMデータ連携 データ取得処理 ECSタスク
|
||||
|
||||
## 前提事項
|
||||
|
||||
### ツールのバージョン
|
||||
|
||||
- Python 3.8.x
|
||||
- PipEnv(Pythonの依存関係管理用モジュール)
|
||||
|
||||
### 開発環境
|
||||
|
||||
- Visual Studio Code
|
||||
|
||||
## 開発環境構築
|
||||
|
||||
※下記の操作は基本的にVSCode上で行います。
|
||||
|
||||
- [ファイル]-[フォルダーを開く]から、当フォルダを選択して開く
|
||||
|
||||
- [Wiki | Pythonの環境構築](https://nds-tyo.backlog.com/alias/wiki/1874930)にて、pyenvの導入まで完了させる
|
||||
- **pyenvの導入はマストではないが、Pythonのバージョンが前提のバージョンと同一であることを確認して開発を進めてください**
|
||||
- **確認しながら開発するのは煩わしいため、導入を強く推奨します。**
|
||||
|
||||
- ローカルのPythonでPipEnvをインストールする
|
||||
|
||||
```sh
|
||||
pip install pipenv
|
||||
```
|
||||
|
||||
- pipenvの仮想環境と依存パッケージをインストール。このとき、初回実行にはpythonの仮想環境のパスがターミナルに表示されるため、控えておく
|
||||
|
||||
```sh
|
||||
# 開発用パッケージも含めてインストール
|
||||
pipenv install --dev
|
||||
```
|
||||
|
||||
- VSCodeのコマンドパレットを[表示]-[コマンドパレット]から開き、`Python: Select interpreter`を選択して実行する
|
||||
- Pythonの実行環境を聞かれるため、先に控えたパスと一致するものを選択する
|
||||
- 出てこない場合、一度VSCodeを閉じて再度開き直す
|
||||
|
||||
- 当フォルダ直下の`.vscode`フォルダ内にある`recommend_settings.json`をコピーし、同フォルダ内に`settings.json`を作成する
|
||||
|
||||
## ローカルでの起動方法
|
||||
|
||||
- 当フォルダ直下の`.vscode`フォルダ内に`launch.json`を作成する
|
||||
- 以下のJSONを入力して保存する
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: Current File",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
// エントリーポイントのファイルに変更すること
|
||||
"program": "<エントリーポイントになるファイル>",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true,
|
||||
// 環境変数が必要な場合に読み込む環境変数ファイル
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- 環境変数が必要な場合、直接設定するか、上記JSONの`"envFile"`に設定されたパスに`.env`ファイルを作成し、環境変数を入力する
|
||||
- キーボードの「F5」キーを押して起動する
|
||||
- デバッグモードで実行されるため、適当なところにブレークポイントを置いてデバッグすることができる
|
||||
|
||||
## ファイル/フォルダ構成
|
||||
|
||||
`[〇〇処理]モジュール`と記載されているファイルは、設計書に記載のシートと一致したPythonファイルです
|
||||
|
||||
```text
|
||||
.
|
||||
├── Dockerfile -- Dockerイメージを作成するためのファイル
|
||||
├── Pipfile -- Pipenv(Pythonの仮想環境管理モジュール)で、依存関係を管理するためのファイル
|
||||
├── Pipfile.lock -- Pipenvでインストールされた依存関係のバージョン固定ファイル
|
||||
├── README.md -- README
|
||||
├── main.py -- CRMデータ取得処理のエントリーポイント
|
||||
├── src/ -- プロダクトコード置き場
|
||||
│ ├── aws/ -- AWSのリソース操作関連のモジュール置き場
|
||||
│ ├── backup_crm_csv_data_process.py -- [CSVバックアップ処理]モジュール
|
||||
│ ├── backup_crm_data_process.py -- [CRM電文データバックアップ処理]モジュール
|
||||
│ ├── check_object_info_process.py -- [オブジェクト情報形式チェック処理]モジュール
|
||||
│ ├── config/ -- 設定ファイル関連のモジュール置き場
|
||||
│ ├── controller.py -- [コントロール処理]モジュール
|
||||
│ ├── convert_crm_csv_data_process.py -- [CSV変換処理]モジュール
|
||||
│ ├── converter/ -- CSV変換処理で実際に変換を行うモジュール置き場
|
||||
│ ├── copy_crm_csv_data_process.py -- [CSVアップロード処理]モジュール
|
||||
│ ├── error/ -- 処理エラー発生時カスタム例外モジュール置き場
|
||||
│ ├── fetch_crm_data_process.py -- [CRMデータ取得処理]モジュール
|
||||
│ ├── parser/ -- JSON設定ファイル読み込み処理モジュール置き場
|
||||
│ ├── prepare_data_fetch_process.py -- [データ取得準備処理]モジュール
|
||||
│ ├── salesforce/ -- SalesforceのAPIリクエストモジュール置き場
|
||||
│ ├── set_datetime_period_process.py -- [データ取得期間設定処理]モジュール
|
||||
│ ├── system_var/ -- 環境変数と定数ファイル置き場
|
||||
│ ├── upload_last_fetch_datetime_process.py -- [前回取得日時ファイル更新処理]モジュール
|
||||
│ ├── upload_result_data_process.py -- [取得処理実施結果アップロード処理]モジュール
|
||||
│ └── util/ -- ユーティリティモジュール置き場
|
||||
│ ├── counter_object.py -- リトライ判定のためのカウントアップクラス
|
||||
│ ├── dict_checker.py -- 辞書型値オブジェクトの設定値チェック用クラス
|
||||
│ ├── execute_datetime.py -- 取得処理開始年月日時分秒の管理クラス
|
||||
│ └── logger.py -- ログ管理クラス
|
||||
│
|
||||
└── tests/ -- テストコード置き場
|
||||
├── test_utils/ -- テストコードで共通的に使用できる関数群
|
||||
├── aws/ -- AWS操作モジュールのテスト
|
||||
├── ... -- src配下のモジュール構成と同じ階層にテストコードを追加していく
|
||||
├── conftest.py -- pytestのフィクスチャやフックを管理するファイル
|
||||
└── docstring_parser.py -- pytest-htmlのレポート出力用のヘルパー
|
||||
```
|
||||
|
||||
## 単体テストについて
|
||||
|
||||
### 前提
|
||||
|
||||
- Pytestを使用する
|
||||
- <https://pypi.org/project/pytest/>
|
||||
- カバレッジも取得したいため、pytest-covも使う
|
||||
- <https://pypi.org/project/pytest-cov/>
|
||||
- レポートを出力するため、pytest-htmlを使う
|
||||
- <https://pypi.org/project/pytest-html/>
|
||||
- S3をモック化したいため、motoをつかう
|
||||
- <https://www.learnaws.org/2020/12/01/test-aws-code/>
|
||||
- CRMはテスト用の環境を使いたいため、newdwh_opeのアドレスでDeveloper組織を登録する
|
||||
|
||||
### テスト環境構築
|
||||
|
||||
- Pipenvの仮想環境下で、以下のコマンドを実行する
|
||||
|
||||
```sh
|
||||
pipenv install --dev
|
||||
```
|
||||
|
||||
- `.env.example`をコピーし、同じ階層に`.env`を作成する
|
||||
- `.env`の以下に示す環境変数の値をDeveloper組織のものに書き換える
|
||||
- CRM_AUTH_DOMAIN
|
||||
- CRM_USER_NAME
|
||||
- CRM_USER_PASSWORD
|
||||
- CRM_USER_SECURITY_TOKEN
|
||||
- 以下のコマンドを実行して単体テストを起動する
|
||||
|
||||
```sh
|
||||
pipenv run test:cov
|
||||
```
|
||||
|
||||
#### 一気通貫テストを実行する場合の設定
|
||||
|
||||
- `.env`の以下に示す環境変数の値をメルク様提供のFullSandboxのものに書き換える
|
||||
- CRM_AUTH_DOMAIN
|
||||
- CRM_USER_NAME
|
||||
- CRM_USER_PASSWORD
|
||||
- CRM_USER_SECURITY_TOKEN
|
||||
- 以下のコマンドを実行して単体テストを起動する
|
||||
|
||||
```sh
|
||||
pipenv run test:walk-through
|
||||
```
|
||||
|
||||
#### 拡張機能:Python Test Explorer UIを導入する場合
|
||||
|
||||
- VSCodeの拡張機能メニューから、「Python Test Explorer for Visual Studio Code」をインストール
|
||||
- コマンドパレットから「Python Configure Tests」を選択、「Pytest」を選択
|
||||
- テストメニュー(フラスコのマーク)から、テストを実行することができるようになる
|
||||
|
||||
#### 各コマンドの説明
|
||||
|
||||
- `pipenv run test`
|
||||
- pytestを使用してテストを実行する
|
||||
- `tests`フォルダに配置されているテストモジュールを対象に、単体テストを実行する
|
||||
- `pipenv run test:cov`
|
||||
- pytestのテスト終了時にカバレッジを収集する
|
||||
- 標準出力とカバレッジファイル(`.coverage`)に出力される
|
||||
- `pipenv run test:report`
|
||||
- pytestのテスト終了時にテスト結果をHTMLで出力する
|
||||
- `.report/unit_test/test_result.html`が出力される
|
||||
- `test:walk-through`
|
||||
- 一気通貫テストが実行される
|
||||
- 上記のテスト以外はスキップされる
|
||||
- `.report/walk_through/test_result.html`に結果のHTMLが出力される
|
||||
|
||||
## 単体テストの追加方法
|
||||
|
||||
- `tests`フォルダ内に、`src`フォルダの構成と同じようにフォルダを作り、`test_<テスト対象のモジュール名.py>`というファイルを作成する
|
||||
- 例:`src/aws/s3.py`をテストする場合は`tests/aws/test_s3.py`というファイル名にする
|
||||
- テスト関数はクラスにまとめて、テストスイートとする
|
||||
- テスト関数にはドキュメントコメントを付け、テスト観点・準備作業・期待値を記載すること
|
||||
- 上記が出力されるスニペットを用意してある。
|
||||
- `""""""`と入力し、「Test docstring (User Snippets)」を選択し、ドキュメントコメントを挿入できる
|
||||
|
||||
```python
|
||||
|
||||
from src.aws.s3 import S3Resource
|
||||
class TestS3Resource:
|
||||
def test_get_object(self, s3_test, s3_):
|
||||
"""
|
||||
Cases:
|
||||
S3からオブジェクトが取得できるか
|
||||
Arranges:
|
||||
- S3をモック化する
|
||||
- 期待値となるファイルを配置する
|
||||
Expects:
|
||||
オブジェクトが取得でき、期待値と正しいこと
|
||||
"""
|
||||
# more code...
|
||||
```
|
||||
8
ecs/crm-datafetch/main.py
Normal file
8
ecs/crm-datafetch/main.py
Normal file
@ -0,0 +1,8 @@
|
||||
from src.controller import controller
|
||||
|
||||
"""CRMデータ取得処理のエントリーポイント"""
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
exit(controller())
|
||||
except Exception:
|
||||
exit(0)
|
||||
3
ecs/crm-datafetch/pytest.ini
Normal file
3
ecs/crm-datafetch/pytest.ini
Normal file
@ -0,0 +1,3 @@
|
||||
[pytest]
|
||||
log_format = %(levelname)s %(asctime)s %(message)s
|
||||
log_date_format = %Y-%m-%d %H:%M:%S
|
||||
0
ecs/crm-datafetch/src/__init__.py
Normal file
0
ecs/crm-datafetch/src/__init__.py
Normal file
0
ecs/crm-datafetch/src/aws/__init__.py
Normal file
0
ecs/crm-datafetch/src/aws/__init__.py
Normal file
101
ecs/crm-datafetch/src/aws/s3.py
Normal file
101
ecs/crm-datafetch/src/aws/s3.py
Normal file
@ -0,0 +1,101 @@
|
||||
import json
|
||||
|
||||
import boto3
|
||||
from src.system_var.constants import (AWS_RESOURCE_S3, S3_CHAR_CODE,
|
||||
S3_RESPONSE_BODY)
|
||||
from src.system_var.environments import (CRM_BACKUP_BUCKET, CRM_CONFIG_BUCKET,
|
||||
CRM_IMPORT_DATA_BACKUP_FOLDER,
|
||||
CRM_IMPORT_DATA_FOLDER,
|
||||
IMPORT_DATA_BUCKET,
|
||||
LAST_FETCH_DATE_FOLDER,
|
||||
OBJECT_INFO_FILENAME,
|
||||
OBJECT_INFO_FOLDER,
|
||||
PROCESS_RESULT_FOLDER,
|
||||
RESPONSE_JSON_BACKUP_FOLDER)
|
||||
|
||||
|
||||
class S3Resource:
|
||||
def __init__(self, bucket_name: str) -> None:
|
||||
self.__s3_resource = boto3.resource(AWS_RESOURCE_S3)
|
||||
self.__s3_bucket = self.__s3_resource.Bucket(bucket_name)
|
||||
|
||||
def get_object(self, object_key: str) -> str:
|
||||
response = self.__s3_bucket.Object(object_key).get()
|
||||
body = response[S3_RESPONSE_BODY].read()
|
||||
return body.decode(S3_CHAR_CODE)
|
||||
|
||||
def put_object(self, object_key: str, data: str) -> None:
|
||||
s3_object = self.__s3_bucket.Object(object_key)
|
||||
s3_object.put(Body=data.encode(S3_CHAR_CODE), ContentEncoding=S3_CHAR_CODE)
|
||||
return
|
||||
|
||||
def copy(self, src_bucket: str, src_key: str, dest_bucket: str, dest_key: str) -> None:
|
||||
copy_source = {'Bucket': src_bucket, 'Key': src_key}
|
||||
self.__s3_resource.meta.client.copy(copy_source, dest_bucket, dest_key)
|
||||
return
|
||||
|
||||
|
||||
class ConfigBucket:
|
||||
__s3_resource: S3Resource = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__s3_resource = S3Resource(CRM_CONFIG_BUCKET)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return CRM_CONFIG_BUCKET
|
||||
|
||||
def get_object_info_file(self) -> str:
|
||||
return self.__s3_resource.get_object(f'{OBJECT_INFO_FOLDER}/{OBJECT_INFO_FILENAME}')
|
||||
|
||||
def get_last_fetch_datetime_file(self, file_path: str) -> str:
|
||||
return self.__s3_resource.get_object(f'{LAST_FETCH_DATE_FOLDER}/{file_path}')
|
||||
|
||||
def put_last_fetch_datetime_file(self, file_path: str, data: str) -> None:
|
||||
self.__s3_resource.put_object(
|
||||
f'{LAST_FETCH_DATE_FOLDER}/{file_path}', data)
|
||||
return
|
||||
|
||||
|
||||
class DataBucket:
|
||||
__s3_resource: S3Resource = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__s3_resource = S3Resource(IMPORT_DATA_BUCKET)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return IMPORT_DATA_BUCKET
|
||||
|
||||
def put_csv(self, file_path: str, data: str) -> None:
|
||||
object_key = f'{CRM_IMPORT_DATA_FOLDER}/{file_path}'
|
||||
self.__s3_resource.put_object(object_key, data)
|
||||
return
|
||||
|
||||
def put_csv_from(self, src_bucket: str, src_key: str):
|
||||
dest_filename = src_key.split('/')[-1]
|
||||
self.__s3_resource.copy(src_bucket, src_key, str(self), f'{CRM_IMPORT_DATA_FOLDER}/{dest_filename}')
|
||||
return
|
||||
|
||||
|
||||
class BackupBucket:
|
||||
__s3_resource: S3Resource = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__s3_resource = S3Resource(CRM_BACKUP_BUCKET)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return CRM_BACKUP_BUCKET
|
||||
|
||||
def put_response_json(self, file_path: str, data: dict) -> None:
|
||||
object_key = f'{RESPONSE_JSON_BACKUP_FOLDER}/{file_path}'
|
||||
self.__s3_resource.put_object(object_key, json.dumps(data, ensure_ascii=False))
|
||||
return
|
||||
|
||||
def put_csv(self, file_path: str, data: str) -> None:
|
||||
object_key = f'{CRM_IMPORT_DATA_BACKUP_FOLDER}/{file_path}'
|
||||
self.__s3_resource.put_object(object_key, data)
|
||||
return
|
||||
|
||||
def put_result_json(self, file_path: str, data: dict) -> None:
|
||||
object_key = f'{PROCESS_RESULT_FOLDER}/{file_path}'
|
||||
self.__s3_resource.put_object(object_key, json.dumps(data, ensure_ascii=False))
|
||||
return
|
||||
47
ecs/crm-datafetch/src/backup_crm_csv_data_process.py
Normal file
47
ecs/crm-datafetch/src/backup_crm_csv_data_process.py
Normal file
@ -0,0 +1,47 @@
|
||||
from src.aws.s3 import BackupBucket
|
||||
from src.config.objects import TargetObject
|
||||
from src.error.exceptions import FileUploadException
|
||||
from src.system_var.constants import CSVBK_JP_NAME
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
from src.util.logger import logger_instance as logger
|
||||
|
||||
|
||||
def backup_crm_csv_data_process(target_object: TargetObject, execute_datetime: ExecuteDateTime, csv_string: str):
|
||||
"""CSVバックアップ処理
|
||||
|
||||
Args:
|
||||
target_object (TargetObject): 取得対象オブジェクト情報インスタンス
|
||||
execute_datetime (ExecuteDateTime): 実行日時取得インスタンス
|
||||
csv_string (str): csvデータ
|
||||
|
||||
Raises:
|
||||
FileUploadException: S3のファイルアップロード失敗
|
||||
"""
|
||||
|
||||
# ① CSVバックアップ処理の開始ログを出力する
|
||||
target_object_name = target_object.object_name
|
||||
upload_file_name = target_object.upload_file_name
|
||||
|
||||
logger.info(
|
||||
f'I-CSVBK-01 [{target_object_name}] のCSVデータのバックアップ処理を開始します ファイル名:[{upload_file_name}.csv]')
|
||||
|
||||
try:
|
||||
# ② CRMバックアップ保管用バケットに、変換後のCSVデータのバックアップを保管する
|
||||
backup_bucket = BackupBucket()
|
||||
backup_bucket.put_csv(
|
||||
f'{execute_datetime.to_path()}/{upload_file_name}.csv', csv_string)
|
||||
|
||||
logger.debug(
|
||||
f'D-CSVBK-02 [{target_object_name}] のCSVデータバックアップ 正常終了')
|
||||
|
||||
except Exception as e:
|
||||
raise FileUploadException(
|
||||
'E-CSVBK-01',
|
||||
CSVBK_JP_NAME, f'[{target_object_name}] CSVデータのバックアップに失敗しました ファイル名:[{upload_file_name}.csv] エラー内容:[{e}]')
|
||||
|
||||
# ③ CSVバックアップ処理の終了ログを出力する
|
||||
logger.info(
|
||||
f'I-CSVBK-03 [{target_object_name}] のCSVデータのバックアップ処理を終了します')
|
||||
|
||||
# ④ 次の処理へ移行する
|
||||
return
|
||||
45
ecs/crm-datafetch/src/backup_crm_data_process.py
Normal file
45
ecs/crm-datafetch/src/backup_crm_data_process.py
Normal file
@ -0,0 +1,45 @@
|
||||
from src.aws.s3 import BackupBucket
|
||||
from src.config.objects import TargetObject
|
||||
from src.error.exceptions import FileUploadException
|
||||
from src.system_var.constants import RESBK_JP_NAME
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
from src.util.logger import logger_instance as logger
|
||||
|
||||
|
||||
def backup_crm_data_process(target_object: TargetObject, sf_object_dict: dict, execute_datetime: ExecuteDateTime):
|
||||
"""CRM電文データバックアップ処理
|
||||
|
||||
Args:
|
||||
target_object (TargetObject): 取得対象オブジェクト名
|
||||
sf_object_dict (dict): Salesforceオブジェクトデータ
|
||||
execute_datetime (ExecuteDateTime): 実行日時取得インスタンス
|
||||
|
||||
Raises:
|
||||
FileUploadException: S3のファイルアップロード失敗
|
||||
"""
|
||||
|
||||
object_name = target_object.object_name
|
||||
# ① CRM電文データバックアップ処理の開始ログを出力する
|
||||
logger.info(f'I-RESBK-01 [{object_name}] のCRM電文データバックアップ処理を開始します')
|
||||
|
||||
try:
|
||||
# ② CRMバックアップ保管用バケットに、CRMから取得したJSONの電文データのバックアップを保管する
|
||||
file_name = f'{execute_datetime.to_path()}/{target_object.upload_file_name}.json'
|
||||
|
||||
backup_bucket = BackupBucket()
|
||||
backup_bucket.put_response_json(file_name, sf_object_dict)
|
||||
|
||||
logger.debug(f'D-RESBK-02 [{object_name}] のJSONデータバックアップ 正常終了')
|
||||
|
||||
except Exception as e:
|
||||
raise FileUploadException(
|
||||
'E-RESBK-01',
|
||||
RESBK_JP_NAME,
|
||||
f'[{object_name}] 電文データのバックアップに失敗しました ファイル名:[{target_object.upload_file_name}.json] エラー内容:[{e}]'
|
||||
)
|
||||
|
||||
# ③ CRM電文データバックアップ処理の終了ログを出力する
|
||||
logger.info(f'I-RESBK-03 [{object_name}] のCRM電文データバックアップ処理を終了します')
|
||||
|
||||
# ④ 次の処理へ移行する
|
||||
return
|
||||
37
ecs/crm-datafetch/src/check_object_info_process.py
Normal file
37
ecs/crm-datafetch/src/check_object_info_process.py
Normal file
@ -0,0 +1,37 @@
|
||||
from src.config.objects import TargetObject
|
||||
from src.error.exceptions import InvalidConfigException
|
||||
from src.system_var.constants import CHK_JP_NAME
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
from src.util.logger import logger_instance as logger
|
||||
|
||||
|
||||
def check_object_info_process(object_info: dict, execute_datetime: ExecuteDateTime):
|
||||
"""オブジェクト情報形式チェック処理
|
||||
|
||||
Args:
|
||||
object_info (dict): 取得対象オブジェクト情報
|
||||
execute_datetime (ExecuteDateTime): 実行日時取得インスタンス
|
||||
|
||||
Raises:
|
||||
InvalidConfigException: オブジェクト情報定義が不正だった場合
|
||||
|
||||
Returns:
|
||||
target_object: 取得対象オブジェクト情報インスタンス
|
||||
"""
|
||||
|
||||
# ① オブジェクト情報形式チェック処理開始ログを出力する
|
||||
logger.info('I-CHK-01 オブジェクト情報形式チェック処理を開始します')
|
||||
|
||||
try:
|
||||
# ② オブジェクト情報形式チェック
|
||||
target_object = TargetObject(object_info, execute_datetime)
|
||||
|
||||
except Exception as e:
|
||||
raise InvalidConfigException(
|
||||
'E-CHK-01', CHK_JP_NAME, f'オブジェクト情報形式チェック処理が失敗しました エラー内容:[{e}]')
|
||||
|
||||
# ③ チェック処理終了ログを出力する
|
||||
logger.info('I-CHK-02 オブジェクト情報形式チェック処理を終了します')
|
||||
|
||||
# ④ 次の処理へ移行する
|
||||
return target_object
|
||||
0
ecs/crm-datafetch/src/config/__init__.py
Normal file
0
ecs/crm-datafetch/src/config/__init__.py
Normal file
147
ecs/crm-datafetch/src/config/objects.py
Normal file
147
ecs/crm-datafetch/src/config/objects.py
Normal file
@ -0,0 +1,147 @@
|
||||
from src.system_var.constants import (COLUMNS_KEY, COLUMNS_TYPE,
|
||||
DATE_PATTERN_EXPECTED_YYYYMMDDTHHMMSSTZ,
|
||||
DATE_PATTERN_YYYYMMDDTHHMMSSTZ,
|
||||
DATETIME_COLUMN_DEFAULT_VALUE,
|
||||
DATETIME_COLUMN_KEY,
|
||||
DATETIME_COLUMN_TYPE, IS_SKIP_KEY,
|
||||
IS_SKIP_TYPE,
|
||||
IS_UPDATE_LAST_FETCH_DATETIME_KEY,
|
||||
IS_UPDATE_LAST_FETCH_DATETIME_TYPE,
|
||||
LAST_FETCH_DATETIME_FILE_NAME_KEY,
|
||||
LAST_FETCH_DATETIME_FILE_NAME_TYPE,
|
||||
LAST_FETCH_DATETIME_FROM_KEY,
|
||||
LAST_FETCH_DATETIME_FROM_TYPE,
|
||||
LAST_FETCH_DATETIME_TO_KEY,
|
||||
LAST_FETCH_DATETIME_TO_TYPE,
|
||||
OBJECT_NAME_KEY, OBJECT_NAME_TYPE,
|
||||
OBJECTS_KEY, OBJECTS_TYPE,
|
||||
UPLOAD_FILE_NAME_KEY,
|
||||
UPLOAD_FILE_NAME_TYPE)
|
||||
from src.util.dict_checker import DictChecker
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
|
||||
class FetchTargetObjects():
|
||||
def __init__(self, object_info_file_dict) -> None:
|
||||
self.__objects = object_info_file_dict
|
||||
self.__dict_checker = DictChecker(self.__objects)
|
||||
self.__validate()
|
||||
self.__i = 0
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
if self.__i == len(self.__objects[OBJECTS_KEY]):
|
||||
raise StopIteration()
|
||||
value = self.__objects[OBJECTS_KEY][self.__i]
|
||||
self.__i += 1
|
||||
return value
|
||||
|
||||
def __validate(self) -> None:
|
||||
self.__dict_checker.assert_key_exist(OBJECTS_KEY)
|
||||
self.__dict_checker.assert_data_type(OBJECTS_KEY, OBJECTS_TYPE)
|
||||
|
||||
|
||||
class TargetObject():
|
||||
def __init__(self, object_info, execute_datetime: ExecuteDateTime) -> None:
|
||||
self.__dict_checker = DictChecker(object_info)
|
||||
self.__object_info = object_info
|
||||
self.__execute_datetime = execute_datetime
|
||||
self.__validate()
|
||||
|
||||
def __validate(self) -> None:
|
||||
self.__validate_required_properties()
|
||||
self.__validate_optional_properties()
|
||||
|
||||
return
|
||||
|
||||
def __validate_required_properties(self) -> None:
|
||||
self.__dict_checker.assert_key_exist(OBJECT_NAME_KEY)
|
||||
self.__dict_checker.assert_data_type(OBJECT_NAME_KEY, OBJECT_NAME_TYPE)
|
||||
self.__dict_checker.assert_key_exist(COLUMNS_KEY)
|
||||
self.__dict_checker.assert_data_type(COLUMNS_KEY, COLUMNS_TYPE)
|
||||
self.__dict_checker.assert_list_empty(COLUMNS_KEY)
|
||||
|
||||
return
|
||||
|
||||
def __validate_optional_properties(self) -> None:
|
||||
if self.__dict_checker.check_key_exist(IS_SKIP_KEY):
|
||||
self.__dict_checker.assert_data_type(IS_SKIP_KEY, IS_SKIP_TYPE)
|
||||
|
||||
if self.__dict_checker.check_key_exist(IS_UPDATE_LAST_FETCH_DATETIME_KEY):
|
||||
self.__dict_checker.assert_data_type(IS_UPDATE_LAST_FETCH_DATETIME_KEY, IS_UPDATE_LAST_FETCH_DATETIME_TYPE)
|
||||
|
||||
if self.__dict_checker.check_key_exist(LAST_FETCH_DATETIME_FILE_NAME_KEY):
|
||||
self.__dict_checker.assert_data_type(LAST_FETCH_DATETIME_FILE_NAME_KEY, LAST_FETCH_DATETIME_FILE_NAME_TYPE)
|
||||
|
||||
if self.__dict_checker.check_key_exist(UPLOAD_FILE_NAME_KEY):
|
||||
self.__dict_checker.assert_data_type(UPLOAD_FILE_NAME_KEY, UPLOAD_FILE_NAME_TYPE)
|
||||
|
||||
if self.__dict_checker.check_key_exist(DATETIME_COLUMN_KEY):
|
||||
self.__dict_checker.assert_data_type(DATETIME_COLUMN_KEY, DATETIME_COLUMN_TYPE)
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def object_name(self) -> str:
|
||||
return self.__object_info[OBJECT_NAME_KEY]
|
||||
|
||||
@property
|
||||
def columns(self) -> list:
|
||||
return self.__object_info[COLUMNS_KEY]
|
||||
|
||||
@property
|
||||
def is_skip(self) -> bool:
|
||||
return self.__object_info[IS_SKIP_KEY] if self.__dict_checker.check_key_exist(IS_SKIP_KEY) else False
|
||||
|
||||
@property
|
||||
def is_update_last_fetch_datetime(self) -> bool:
|
||||
if self.__dict_checker.check_key_exist(IS_UPDATE_LAST_FETCH_DATETIME_KEY):
|
||||
return self.__object_info[IS_UPDATE_LAST_FETCH_DATETIME_KEY]
|
||||
return True
|
||||
|
||||
@property
|
||||
def last_fetch_datetime_file_name(self) -> str:
|
||||
if self.__dict_checker.check_key_exist(LAST_FETCH_DATETIME_FILE_NAME_KEY):
|
||||
return self.__object_info[LAST_FETCH_DATETIME_FILE_NAME_KEY]
|
||||
return f'{self.__object_info[OBJECT_NAME_KEY]}.json'
|
||||
|
||||
@property
|
||||
def upload_file_name(self) -> str:
|
||||
if self.__dict_checker.check_key_exist(UPLOAD_FILE_NAME_KEY):
|
||||
return self.__object_info[UPLOAD_FILE_NAME_KEY].format(execute_datetime=self.__execute_datetime.format_date())
|
||||
return f'CRM_{self.__object_info[OBJECT_NAME_KEY]}_{self.__execute_datetime.format_date()}'
|
||||
|
||||
@property
|
||||
def datetime_column(self) -> str:
|
||||
return self.__object_info[DATETIME_COLUMN_KEY] if self.__dict_checker.check_key_exist(DATETIME_COLUMN_KEY) else DATETIME_COLUMN_DEFAULT_VALUE
|
||||
|
||||
|
||||
class LastFetchDatetime():
|
||||
def __init__(self, last_fetch_datetime_file_dict, execute_datetime) -> None:
|
||||
self.__dict_checker = DictChecker(last_fetch_datetime_file_dict)
|
||||
self.__execute_datetime = execute_datetime
|
||||
self.__last_fetch_datetime_file_dict = last_fetch_datetime_file_dict
|
||||
self.__validate()
|
||||
|
||||
def __validate(self) -> None:
|
||||
self.__dict_checker.assert_key_exist(LAST_FETCH_DATETIME_FROM_KEY)
|
||||
self.__dict_checker.assert_data_type(LAST_FETCH_DATETIME_FROM_KEY, LAST_FETCH_DATETIME_FROM_TYPE)
|
||||
self.__dict_checker.assert_match_pattern(LAST_FETCH_DATETIME_FROM_KEY, DATE_PATTERN_YYYYMMDDTHHMMSSTZ,
|
||||
DATE_PATTERN_EXPECTED_YYYYMMDDTHHMMSSTZ)
|
||||
if self.__dict_checker.check_key_exist(LAST_FETCH_DATETIME_TO_KEY):
|
||||
self.__dict_checker.assert_data_type(LAST_FETCH_DATETIME_TO_KEY, LAST_FETCH_DATETIME_TO_TYPE)
|
||||
self.__dict_checker.assert_match_pattern(LAST_FETCH_DATETIME_TO_KEY, DATE_PATTERN_YYYYMMDDTHHMMSSTZ,
|
||||
DATE_PATTERN_EXPECTED_YYYYMMDDTHHMMSSTZ)
|
||||
return
|
||||
|
||||
@property
|
||||
def last_fetch_datetime_from(self) -> str:
|
||||
return self.__last_fetch_datetime_file_dict[LAST_FETCH_DATETIME_FROM_KEY]
|
||||
|
||||
@property
|
||||
def last_fetch_datetime_to(self) -> str:
|
||||
if self.__dict_checker.check_key_exist(LAST_FETCH_DATETIME_TO_KEY):
|
||||
return self.__last_fetch_datetime_file_dict[LAST_FETCH_DATETIME_TO_KEY]
|
||||
return str(self.__execute_datetime)
|
||||
202
ecs/crm-datafetch/src/controller.py
Normal file
202
ecs/crm-datafetch/src/controller.py
Normal file
@ -0,0 +1,202 @@
|
||||
import gc
|
||||
|
||||
from src.backup_crm_csv_data_process import backup_crm_csv_data_process
|
||||
from src.backup_crm_data_process import backup_crm_data_process
|
||||
from src.check_object_info_process import check_object_info_process
|
||||
from src.config.objects import FetchTargetObjects
|
||||
from src.convert_crm_csv_data_process import convert_crm_csv_data_process
|
||||
from src.copy_crm_csv_data_process import copy_crm_csv_data_process
|
||||
from src.error.exceptions import MeDaCaCRMDataFetchException
|
||||
from src.fetch_crm_data_process import fetch_crm_data_process
|
||||
from src.prepare_data_fetch_process import prepare_data_fetch_process
|
||||
from src.set_datetime_period_process import set_datetime_period_process
|
||||
from src.system_var.constants import OBJECT_NAME_KEY
|
||||
from src.upload_last_fetch_datetime_process import \
|
||||
upload_last_fetch_datetime_process
|
||||
from src.upload_result_data_process import upload_result_data_process
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
from src.util.logger import logger_instance as logger
|
||||
|
||||
|
||||
def controller() -> None:
|
||||
"""コントロール処理"""
|
||||
|
||||
try:
|
||||
# ① CRMデータ取得処理開始ログを出力する
|
||||
logger.info('I-CTRL-01 CRMデータ取得処理を開始します')
|
||||
|
||||
# ② データ取得準備処理を呼び出す
|
||||
logger.info('I-CTRL-02 データ取得準備処理呼び出し')
|
||||
|
||||
fetch_target_objects, execute_datetime, process_result = prepare_data_fetch_process()
|
||||
|
||||
# ③ object_infoのobjectsキーの値の件数分ループする
|
||||
logger.info('I-CTRL-03 取得対象オブジェクトのループ処理開始')
|
||||
|
||||
process_result = _fetch_crm_data(fetch_target_objects, execute_datetime, process_result)
|
||||
|
||||
# ④ すべてのオブジェクトの処理が完了したことと、オブジェクト毎の処理結果をログに出力する
|
||||
logger.info(f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{process_result}]')
|
||||
|
||||
# 最終結果が0件(1件も処理されていない)の場合、ログ出力して処理を終了する
|
||||
if len(process_result.keys()) == 0:
|
||||
logger.info('I-CTRL-21 処理対象のデータが存在しませんでした')
|
||||
return 0
|
||||
|
||||
# ⑤ 取得処理実施結果アップロード処理を呼び出す
|
||||
logger.info('I-CTRL-18 CRM_取得処理実施結果ファイルアップロード処理開始')
|
||||
upload_result_data_process(process_result, execute_datetime)
|
||||
|
||||
# ⑥ 最終結果をチェックし、チェック結果をログに出力
|
||||
_check_process_result(process_result)
|
||||
|
||||
return 0
|
||||
|
||||
except MeDaCaCRMDataFetchException as e:
|
||||
logger.error(f'E-ERR-01 [{e.func_name}]でエラーが発生したため、処理を終了します')
|
||||
logger.exception(f'{e.error_id} {e}')
|
||||
raise e
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f'E-ERR-02 予期せぬエラーが発生したため、処理を終了します エラー内容: [{e}]')
|
||||
raise e
|
||||
|
||||
finally:
|
||||
# ⑦ CRMデータ取得処理終了ログを出力する
|
||||
logger.info('I-CTRL-20 CRMデータ取得処理を終了します')
|
||||
|
||||
|
||||
def _fetch_crm_data(fetch_target_objects: FetchTargetObjects, execute_datetime: ExecuteDateTime, process_result: dict):
|
||||
"""取得対象オブジェクト情報をループし、1オブジェクトごとのデータを取得する
|
||||
|
||||
Args:
|
||||
fetch_target_objects (FetchTargetObjects): CRMオブジェクト情報インスタンス
|
||||
execute_datetime (ExecuteDateTime): 実行日時取得インスタンス
|
||||
process_result (dict): 取得処理実行結果辞書オブジェクト
|
||||
|
||||
Returns:
|
||||
process_result: 取得処理実行結果辞書オブジェクト
|
||||
"""
|
||||
|
||||
for object_info in fetch_target_objects:
|
||||
try:
|
||||
# 1.処理結果出力用のマップの初期化
|
||||
process_result[object_info.get(OBJECT_NAME_KEY)] = 'fail'
|
||||
|
||||
_fetch_crm_data_per_object(object_info, execute_datetime)
|
||||
|
||||
# 16.処理結果出力用のマップの更新
|
||||
process_result[object_info.get(OBJECT_NAME_KEY)] = 'success'
|
||||
|
||||
except MeDaCaCRMDataFetchException as e:
|
||||
logger.info(f'{e.error_id} {e}')
|
||||
logger.info(
|
||||
f'I-ERR-03 [{object_info.get(OBJECT_NAME_KEY)}] の[{e.func_name}]でエラーが発生しました 次のオブジェクトの処理に移行します', exc_info=True)
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
logger.info(
|
||||
f'I-ERR-04 [{object_info.get(OBJECT_NAME_KEY)}] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [{e}]', exc_info=True)
|
||||
continue
|
||||
|
||||
return process_result
|
||||
|
||||
|
||||
def _fetch_crm_data_per_object(object_info: dict, execute_datetime: ExecuteDateTime) -> None:
|
||||
"""オブジェクトごとにCRMのデータを取得し、取込フォルダにアップロードする
|
||||
|
||||
Args:
|
||||
object_info (dict): 取得対象オブジェクト情報
|
||||
execute_datetime (ExecuteDateTime): 実行日時取得インスタンス
|
||||
"""
|
||||
|
||||
# 2. 対象オブジェクト情報の内容をログに出力する
|
||||
logger.debug(f'D-CTRL-04 対象のオブジェクト情報を出力します オブジェクト情報:[{object_info}]')
|
||||
|
||||
# 3. オブジェクト情報形式チェック処理を呼び出す
|
||||
logger.info('I-CTRL-05 オブジェクト情報形式チェック処理呼び出し')
|
||||
|
||||
target_object = check_object_info_process(object_info, execute_datetime)
|
||||
target_object_name = target_object.object_name
|
||||
|
||||
# 4. 処理対象のオブジェクト名をログ出力する
|
||||
logger.info(
|
||||
f'I-CTRL-06 [{target_object_name}]のデータ取得を開始します')
|
||||
|
||||
# 5. オブジェクト情報.is_skipがTrueの場合、次のオブジェクトの処理に移行する
|
||||
if target_object.is_skip is True:
|
||||
logger.info(
|
||||
f'I-CTRL-07 [{target_object_name}]のデータ取得処理をスキップします')
|
||||
return
|
||||
|
||||
# 6. データ取得期間設定処理を呼び出す
|
||||
logger.info(
|
||||
f'I-CTRL-08 [{target_object_name}]のデータ取得期間設定処理呼び出し')
|
||||
|
||||
last_fetch_datetime = set_datetime_period_process(target_object, execute_datetime)
|
||||
|
||||
# 7. CRMデータ取得処理を呼び出す
|
||||
logger.info(
|
||||
f'I-CTRL-09 [{target_object_name}]のデータ取得処理呼び出し')
|
||||
|
||||
crm_data_response = fetch_crm_data_process(target_object, last_fetch_datetime)
|
||||
|
||||
# 取得件数が0件の場合、次のオブジェクトの処理に移行する
|
||||
if len(crm_data_response) == 0:
|
||||
logger.info(
|
||||
f'I-CTRL-22 [{target_object_name}]のレコード件数が0件のため、ファイルアップロードをスキップします')
|
||||
return
|
||||
|
||||
# 8. 出力ファイル名をログ出力する
|
||||
logger.info(
|
||||
f'I-CTRL-10 [{target_object_name}] の出力ファイル名は [{target_object.upload_file_name}] となります')
|
||||
|
||||
# 9. CRM電文データバックアップ処理を呼び出す
|
||||
logger.info(
|
||||
f'I-CTRL-11 [{target_object_name}] CRM電文データバックアップ処理呼び出し')
|
||||
backup_crm_data_process(target_object, crm_data_response, execute_datetime)
|
||||
|
||||
# 10. CSV変換処理を呼び出す
|
||||
logger.info(
|
||||
f'I-CTRL-12 [{target_object.object_name}] CSV変換処理呼び出し')
|
||||
csv_string = convert_crm_csv_data_process(target_object, crm_data_response)
|
||||
|
||||
# 11. CSVバックアップ処理を呼び出す
|
||||
logger.info(
|
||||
f'I-CTRL-13 [{target_object_name}] CSVデータバックアップ処理呼び出し')
|
||||
backup_crm_csv_data_process(target_object, execute_datetime, csv_string)
|
||||
|
||||
# 12. CSVアップロード処理を呼び出す
|
||||
logger.info(
|
||||
f'I-CTRL-14 [{target_object_name}] CSVデータアップロード処理呼び出し')
|
||||
copy_crm_csv_data_process(target_object, execute_datetime)
|
||||
|
||||
# 13. メモリ解放
|
||||
del crm_data_response
|
||||
del csv_string
|
||||
gc.collect()
|
||||
|
||||
# 14. 前回取得日時ファイル更新処理を呼びだす
|
||||
logger.info(
|
||||
f'I-CTRL-15 [{target_object_name}] 前回取得日時ファイル更新処理呼び出し')
|
||||
upload_last_fetch_datetime_process(target_object, last_fetch_datetime)
|
||||
|
||||
# 15. 1オブジェクトのアップロードが完了した旨をログに出力する
|
||||
logger.info(f'I-CTRL-16 [{target_object_name}] 処理正常終了')
|
||||
|
||||
return
|
||||
|
||||
|
||||
def _check_process_result(process_result: dict) -> None:
|
||||
"""取得処理結果がすべて成功か、一部失敗しているかを判定し、ログ出力する
|
||||
|
||||
Args:
|
||||
process_result (dict): 取得処理結果辞書オブジェクト
|
||||
"""
|
||||
if not all([v == 'success' for v in process_result.values()]):
|
||||
logger.error('E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください')
|
||||
return
|
||||
|
||||
logger.info('I-CTRL-19 すべてのデータの取得に成功しました')
|
||||
|
||||
return
|
||||
42
ecs/crm-datafetch/src/convert_crm_csv_data_process.py
Normal file
42
ecs/crm-datafetch/src/convert_crm_csv_data_process.py
Normal file
@ -0,0 +1,42 @@
|
||||
from src.config.objects import TargetObject
|
||||
from src.converter.converter import CSVStringConverter
|
||||
from src.error.exceptions import DataConvertException
|
||||
from src.system_var.constants import CONV_JP_NAME
|
||||
from src.util.logger import logger_instance as logger
|
||||
|
||||
|
||||
def convert_crm_csv_data_process(target_object: TargetObject, crm_data_response: dict):
|
||||
"""CSV変換処理
|
||||
|
||||
Args:
|
||||
target_object (TargetObject): 取得対象オブジェクト情報インスタンス
|
||||
crm_data_response (dict): Salesforceオブジェクトデータ
|
||||
|
||||
Raises:
|
||||
DataConvertException: データ変換が失敗した場合
|
||||
|
||||
Returns:
|
||||
csv_string: csvデータ
|
||||
"""
|
||||
|
||||
# ① CSV変換処理の開始ログを出力する
|
||||
target_object_name = target_object.object_name
|
||||
|
||||
logger.info(f'I-CONV-01 [{target_object_name}] のCSV変換処理を開始します')
|
||||
|
||||
try:
|
||||
# ② CSV変換
|
||||
csv_string_converter = CSVStringConverter(target_object, crm_data_response)
|
||||
csv_string = csv_string_converter.convert()
|
||||
|
||||
logger.debug(f'D-CONV-02 [{target_object_name}] のCSV変換処理 正常終了')
|
||||
|
||||
except Exception as e:
|
||||
raise DataConvertException(
|
||||
'E-CONV-01', CONV_JP_NAME, f'[{target_object_name}] CSV変換に失敗しました エラー内容:[{e}]')
|
||||
|
||||
# ③ CSV変換処理の終了ログを出力する
|
||||
logger.info(f'I-CONV-03 [{target_object_name}] のCSV変換処理を終了します')
|
||||
|
||||
# ④ 次の処理へ移行する
|
||||
return csv_string
|
||||
0
ecs/crm-datafetch/src/converter/__init__.py
Normal file
0
ecs/crm-datafetch/src/converter/__init__.py
Normal file
81
ecs/crm-datafetch/src/converter/convert_strategy.py
Normal file
81
ecs/crm-datafetch/src/converter/convert_strategy.py
Normal file
@ -0,0 +1,81 @@
|
||||
import json
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime
|
||||
|
||||
from dateutil.tz import gettz
|
||||
from src.system_var.constants import (CRM_DATETIME_FORMAT, CSV_FALSE_VALUE,
|
||||
CSV_TRUE_VALUE,
|
||||
DATE_PATTERN_YYYYMMDDHHMMSSFFF_UTC,
|
||||
YYYYMMDDHHMMSS)
|
||||
from src.system_var.environments import CONVERT_TZ
|
||||
|
||||
|
||||
class ConvertStrategyFactory:
|
||||
def __init__(self) -> None:
|
||||
self.__none_value_convert_strategy = NoneValueConvertStrategy()
|
||||
self.__boolean_convert_strategy = BooleanConvertStrategy()
|
||||
self.__datetime_convert_strategy = DatetimeConvertStrategy()
|
||||
self.__int_convert_strategy = IntConvertStrategy()
|
||||
self.__string_convert_strategy = StringConvertStrategy()
|
||||
self.__dict_convert_strategy = DictConvertStrategy()
|
||||
|
||||
def create(self, value):
|
||||
|
||||
if value is None:
|
||||
convert_strategy = self.__none_value_convert_strategy
|
||||
|
||||
elif type(value) == bool:
|
||||
convert_strategy = self.__boolean_convert_strategy
|
||||
|
||||
elif type(value) == str and re.fullmatch(DATE_PATTERN_YYYYMMDDHHMMSSFFF_UTC, value):
|
||||
convert_strategy = self.__datetime_convert_strategy
|
||||
|
||||
elif type(value) == int:
|
||||
convert_strategy = self.__int_convert_strategy
|
||||
|
||||
elif type(value) == dict or type(value) == OrderedDict:
|
||||
convert_strategy = self.__dict_convert_strategy
|
||||
else:
|
||||
convert_strategy = self.__string_convert_strategy
|
||||
|
||||
return convert_strategy
|
||||
|
||||
|
||||
class NoneValueConvertStrategy:
|
||||
def convert_value(self, convert_value: None) -> str:
|
||||
"""Noneを''空文字に変換する処理"""
|
||||
return ''
|
||||
|
||||
|
||||
class BooleanConvertStrategy:
|
||||
def convert_value(self, convert_value: str) -> bool:
|
||||
"""booleanを数値に変換する処理"""
|
||||
return CSV_TRUE_VALUE if convert_value is True else CSV_FALSE_VALUE
|
||||
|
||||
|
||||
class DatetimeConvertStrategy:
|
||||
def convert_value(self, convert_value: str) -> str:
|
||||
"""UTCのdatetime文字列をJSTの日時文字列に変換する処理"""
|
||||
# データ登録処理がJSTとして登録するため、変換処理内で事前にJSTの日時文字列に変換する
|
||||
return datetime.strptime(convert_value, CRM_DATETIME_FORMAT).astimezone(gettz(CONVERT_TZ)).strftime(YYYYMMDDHHMMSS)
|
||||
|
||||
|
||||
class IntConvertStrategy:
|
||||
def convert_value(self, convert_value: int):
|
||||
"""int型を変換せずに返す処理"""
|
||||
# ConvertStrategyFactoryにて型チェックを行っているため値を変換せずに返す
|
||||
return convert_value
|
||||
|
||||
|
||||
class StringConvertStrategy:
|
||||
def convert_value(self, convert_value: str):
|
||||
"""string型を変換せずに返す処理"""
|
||||
# ConvertStrategyFactoryにて型チェックを行っているため値を変換せずに返す
|
||||
return convert_value
|
||||
|
||||
|
||||
class DictConvertStrategy:
|
||||
def convert_value(self, convert_value: dict):
|
||||
"""dict型の項目を文字列に変換して返す処理"""
|
||||
return json.dumps(convert_value, ensure_ascii=False)
|
||||
75
ecs/crm-datafetch/src/converter/converter.py
Normal file
75
ecs/crm-datafetch/src/converter/converter.py
Normal file
@ -0,0 +1,75 @@
|
||||
import csv
|
||||
import io
|
||||
|
||||
from src.config.objects import TargetObject
|
||||
from src.converter.convert_strategy import ConvertStrategyFactory
|
||||
|
||||
|
||||
class CSVStringConverter:
|
||||
def __init__(self, target_object: TargetObject, sf_object_jsons: dict) -> None:
|
||||
self.__target_object = target_object
|
||||
self.__sf_object_jsons = sf_object_jsons
|
||||
self.__convert_strategy_factory = ConvertStrategyFactory()
|
||||
|
||||
def convert(self) -> str:
|
||||
extracted_sf_object_jsons = self.__extract_sf_object_jsons()
|
||||
csv_data = self.__convert_to_csv(extracted_sf_object_jsons)
|
||||
csv_string = self.__write_csv_string(csv_data)
|
||||
return csv_string
|
||||
|
||||
def __extract_sf_object_jsons(self) -> list:
|
||||
try:
|
||||
extracted_sf_object_jsons = []
|
||||
for sf_object_json in self.__sf_object_jsons:
|
||||
extracted_sf_object_jsons.append(
|
||||
self.__extract_necessary_props_from(sf_object_json))
|
||||
|
||||
return extracted_sf_object_jsons
|
||||
|
||||
except Exception as e:
|
||||
raise Exception('必要なjsonのデータ成形に失敗しました', e)
|
||||
|
||||
def __extract_necessary_props_from(self, sf_object_json) -> dict:
|
||||
clone_sf_object = {**sf_object_json}
|
||||
|
||||
del clone_sf_object['attributes']
|
||||
|
||||
uppercase_key_sf_object = {
|
||||
k.upper(): v for k, v in clone_sf_object.items()}
|
||||
|
||||
return uppercase_key_sf_object
|
||||
|
||||
def __convert_to_csv(self, extracted_sf_object_jsons) -> list:
|
||||
try:
|
||||
columns = self.__target_object.columns
|
||||
csv_data = []
|
||||
for i, json_object in enumerate(extracted_sf_object_jsons, 1):
|
||||
csv_row = []
|
||||
for column in columns:
|
||||
v = json_object[column.upper()]
|
||||
|
||||
convert_strategy = self.__convert_strategy_factory.create(v)
|
||||
converted_value = convert_strategy.convert_value(v)
|
||||
|
||||
csv_row.append(converted_value)
|
||||
|
||||
csv_data.append(csv_row)
|
||||
return csv_data
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(
|
||||
f'CSV変換に失敗しました カラム名:[{column}] 行番号: [{i}] エラー内容:[{e}]')
|
||||
|
||||
def __write_csv_string(self, csv_data) -> str:
|
||||
try:
|
||||
with io.StringIO(newline='') as string_stream:
|
||||
writer = csv.writer(string_stream, delimiter=',', lineterminator='\r\n',
|
||||
doublequote=True, quotechar='"', quoting=csv.QUOTE_ALL, strict=True)
|
||||
writer.writerow(self.__target_object.columns)
|
||||
writer.writerows(csv_data)
|
||||
csv_value = string_stream.getvalue()
|
||||
|
||||
return csv_value
|
||||
|
||||
except Exception as e:
|
||||
raise Exception('CSVデータの出力に失敗しました', e)
|
||||
46
ecs/crm-datafetch/src/copy_crm_csv_data_process.py
Normal file
46
ecs/crm-datafetch/src/copy_crm_csv_data_process.py
Normal file
@ -0,0 +1,46 @@
|
||||
from src.aws.s3 import BackupBucket, DataBucket
|
||||
from src.config.objects import TargetObject
|
||||
from src.error.exceptions import FileUploadException
|
||||
from src.system_var.constants import UPLD_JP_NAME
|
||||
from src.system_var.environments import CRM_IMPORT_DATA_BACKUP_FOLDER
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
from src.util.logger import logger_instance as logger
|
||||
|
||||
|
||||
def copy_crm_csv_data_process(target_object: TargetObject, execute_datetime: ExecuteDateTime):
|
||||
"""CSVアップロード処理
|
||||
|
||||
Args:
|
||||
target_object (TargetObject): 取得対象オブジェクト情報インスタンス
|
||||
execute_datetime (ExecuteDateTime): 実行日時取得インスタンス
|
||||
|
||||
Raises:
|
||||
FileUploadException: S3のファイルアップロード失敗
|
||||
"""
|
||||
|
||||
# ① CSVデータアップロード処理の開始ログを出力する
|
||||
target_object_name = target_object.object_name
|
||||
upload_file_name = target_object.upload_file_name
|
||||
|
||||
logger.info(
|
||||
f'I-UPLD-01 [{target_object_name}] のCSVデータアップロード処理を開始します ファイル名:[{upload_file_name}.csv]')
|
||||
|
||||
try:
|
||||
# ② CRMバックアップ保管用バケットに保管した変換後のCSVデータをデータ取込バケットにコピーする
|
||||
data_bucket = DataBucket()
|
||||
backup_bucket = BackupBucket()
|
||||
data_bucket.put_csv_from(str(backup_bucket), f'{CRM_IMPORT_DATA_BACKUP_FOLDER}/{execute_datetime.to_path()}/{upload_file_name}.csv')
|
||||
|
||||
logger.debug(
|
||||
f'D-UPLD-02 [{target_object_name}] のCSVデータアップロード 正常終了')
|
||||
|
||||
except Exception as e:
|
||||
raise FileUploadException(
|
||||
'E-UPLD-01', UPLD_JP_NAME, f'[{target_object_name}] CSVデータのアップロードに失敗しました ファイル名:[{upload_file_name}.csv] エラー内容:[{e}]')
|
||||
|
||||
# ③ CSVデータアップロード処理の終了ログを出力する
|
||||
logger.info(
|
||||
f'I-UPLD-03 [{target_object_name}] のCSVデータのアップロード処理を終了します')
|
||||
|
||||
# ④ 次の処理へ移行する
|
||||
return
|
||||
0
ecs/crm-datafetch/src/error/__init__.py
Normal file
0
ecs/crm-datafetch/src/error/__init__.py
Normal file
35
ecs/crm-datafetch/src/error/exceptions.py
Normal file
35
ecs/crm-datafetch/src/error/exceptions.py
Normal file
@ -0,0 +1,35 @@
|
||||
from abc import ABCMeta
|
||||
|
||||
|
||||
class MeDaCaCRMDataFetchException(Exception, metaclass=ABCMeta):
|
||||
"""MeDaCaシステム固有のカスタムエラークラス"""
|
||||
|
||||
def __init__(self, error_id: str, func_name: str, message: str) -> None:
|
||||
super().__init__(message)
|
||||
self.func_name = func_name
|
||||
self.error_id = error_id
|
||||
|
||||
|
||||
class FileNotFoundException(MeDaCaCRMDataFetchException):
|
||||
"""S3のファイルが見つからない場合の例外"""
|
||||
pass
|
||||
|
||||
|
||||
class FileUploadException(MeDaCaCRMDataFetchException):
|
||||
"""S3のファイルアップロード失敗の例外"""
|
||||
pass
|
||||
|
||||
|
||||
class InvalidConfigException(MeDaCaCRMDataFetchException):
|
||||
"""Configのバリデーションチェック失敗の例外"""
|
||||
pass
|
||||
|
||||
|
||||
class DataConvertException(MeDaCaCRMDataFetchException):
|
||||
"""データ変換が失敗した場合の例外"""
|
||||
pass
|
||||
|
||||
|
||||
class SalesforceAPIException(MeDaCaCRMDataFetchException):
|
||||
"""SalesforceのAPI実行失敗が発生した場合の例外"""
|
||||
pass
|
||||
147
ecs/crm-datafetch/src/fetch_crm_data_process.py
Normal file
147
ecs/crm-datafetch/src/fetch_crm_data_process.py
Normal file
@ -0,0 +1,147 @@
|
||||
from requests.exceptions import ConnectTimeout, ReadTimeout
|
||||
from tenacity import retry, stop_after_attempt
|
||||
from tenacity.wait import wait_exponential
|
||||
|
||||
from src.config.objects import LastFetchDatetime, TargetObject
|
||||
from src.error.exceptions import SalesforceAPIException
|
||||
from src.salesforce.salesforce_api import SalesforceApiClient
|
||||
from src.salesforce.soql_builder import SOQLBuilder
|
||||
from src.system_var.constants import FETCH_JP_NAME
|
||||
from src.system_var.environments import (
|
||||
CRM_AUTH_TIMEOUT, CRM_FETCH_RECORD_MAX_RETRY_ATTEMPT,
|
||||
CRM_FETCH_RECORD_RETRY_INTERVAL, CRM_FETCH_RECORD_RETRY_MAX_INTERVAL,
|
||||
CRM_FETCH_RECORD_RETRY_MIN_INTERVAL, CRM_FETCH_RECORD_TIMEOUT,
|
||||
CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT,
|
||||
CRM_GET_RECORD_COUNT_RETRY_INTERVAL,
|
||||
CRM_GET_RECORD_COUNT_RETRY_MAX_INTERVAL,
|
||||
CRM_GET_RECORD_COUNT_RETRY_MIN_INTERVAL, CRM_GET_RECORD_COUNT_TIMEOUT)
|
||||
from src.util.counter_object import CounterObject
|
||||
from src.util.logger import logger_instance as logger
|
||||
|
||||
|
||||
def fetch_crm_data_process(target_object: TargetObject, last_fetch_datetime: LastFetchDatetime):
|
||||
"""CRMデータ取得処理
|
||||
|
||||
Args:
|
||||
target_object (TargetObject): 取得対象オブジェクト情報インスタンス
|
||||
last_fetch_datetime (LastFetchDatetime): データ取得期間設定インスタンス
|
||||
|
||||
Raises:
|
||||
SalesforceAPIException: SalesforceのAPI実行失敗が発生した場合
|
||||
|
||||
Returns:
|
||||
crm_data_response: Salesforceオブジェクトデータ
|
||||
"""
|
||||
|
||||
# ① CRMデータ取得処理開始ログを出力する
|
||||
logger.info(
|
||||
f'I-FETCH-01 [{target_object.object_name}] のCRMからのデータ取得処理を開始します')
|
||||
|
||||
target_object_name = target_object.object_name
|
||||
|
||||
# リトライ回数判定用のカウンタオブジェクトを生成
|
||||
# @retryデコレータを利用した関数のリトライ処理で、基本データ型だとリトライ回数をカウントすることができないため、オブジェクト化する
|
||||
count_counter = CounterObject(1)
|
||||
data_counter = CounterObject(1)
|
||||
|
||||
try:
|
||||
# ② 取得対象オブジェクトの取得期間内のレコード件数を取得する
|
||||
logger.info(f'I-FETCH-02 [{target_object_name}] の件数取得を開始します')
|
||||
|
||||
soql_builder = SOQLBuilder(target_object, last_fetch_datetime)
|
||||
count_soql = soql_builder.create_count_soql()
|
||||
|
||||
record_count = fetch_record_count_retry(count_soql, target_object_name, count_counter)
|
||||
|
||||
logger.info(f'I-FETCH-03 [{target_object_name}] の件数:[{record_count}]')
|
||||
|
||||
except Exception as e:
|
||||
raise SalesforceAPIException(
|
||||
'E-FETCH-01', FETCH_JP_NAME, f'[{target_object_name}] の件数取得に失敗しました エラー内容:[{e}]')
|
||||
|
||||
try:
|
||||
# ③ 取得対象オブジェクトのレコードを取得する
|
||||
logger.info(f'I-FETCH-04 [{target_object_name}] のレコード取得を開始します')
|
||||
|
||||
fetch_soql = soql_builder.create_fetch_soql()
|
||||
|
||||
crm_data_response = fetch_sf_data_retry(fetch_soql, target_object_name, data_counter)
|
||||
|
||||
logger.info(f'I-FETCH-05 [{target_object_name}] のレコード取得が成功しました')
|
||||
|
||||
except Exception as e:
|
||||
raise SalesforceAPIException(
|
||||
'E-FETCH-02', FETCH_JP_NAME, f'[{target_object_name}] のレコード取得に失敗しました エラー内容:[{e}]')
|
||||
|
||||
# ④ CRMデータ取得処理終了ログを出力する
|
||||
logger.info(f'I-FETCH-06 [{target_object_name}] のCRMからのデータ取得処理を終了します')
|
||||
|
||||
# ⑤ 次の処理へ移行する
|
||||
return crm_data_response
|
||||
|
||||
|
||||
@retry(
|
||||
wait=wait_exponential(multiplier=CRM_GET_RECORD_COUNT_RETRY_INTERVAL,
|
||||
min=CRM_GET_RECORD_COUNT_RETRY_MIN_INTERVAL, max=CRM_GET_RECORD_COUNT_RETRY_MAX_INTERVAL),
|
||||
stop=stop_after_attempt(CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT))
|
||||
def fetch_record_count_retry(soql: str, target_object_name: str, count_counter: CounterObject):
|
||||
try:
|
||||
salesforce_api_client = SalesforceApiClient()
|
||||
return salesforce_api_client.fetch_sf_count(soql)
|
||||
|
||||
except ConnectTimeout as e:
|
||||
# 「リトライします」のメッセージ出力後、リトライせず例外終了になってしまうことを防ぐため、カウンタによる回数の判定を行う
|
||||
if count_counter.describe() < CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT:
|
||||
count_counter.increment(1)
|
||||
logger.warning(f'W-FETCH-01 CRMの接続処理がタイムアウトしため、リトライします:[{CRM_AUTH_TIMEOUT}] エラー内容:[{e}]')
|
||||
raise e
|
||||
|
||||
except ReadTimeout as e:
|
||||
|
||||
# 「リトライします」のメッセージ出力後、リトライせず例外終了になってしまうことを防ぐため、カウンタによる回数の判定を行う
|
||||
if count_counter.describe() < CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT:
|
||||
count_counter.increment(1)
|
||||
logger.warning(
|
||||
f'W-FETCH-02 [{target_object_name}] の件数取得処理がタイムアウトしたため、リトライします:[{CRM_GET_RECORD_COUNT_TIMEOUT}] エラー内容:[{e}]')
|
||||
raise e
|
||||
|
||||
except Exception as e:
|
||||
# 「リトライします」のメッセージ出力後、リトライせず例外終了になってしまうことを防ぐため、カウンタによる回数の判定を行う
|
||||
if count_counter.describe() < CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT:
|
||||
count_counter.increment(1)
|
||||
logger.warning(
|
||||
f'W-FETCH-03 [{target_object_name}] の件数取得に失敗したため、リトライします エラー内容:[{e}]')
|
||||
raise e
|
||||
|
||||
|
||||
@retry(
|
||||
wait=wait_exponential(multiplier=CRM_FETCH_RECORD_RETRY_INTERVAL,
|
||||
min=CRM_FETCH_RECORD_RETRY_MIN_INTERVAL, max=CRM_FETCH_RECORD_RETRY_MAX_INTERVAL),
|
||||
stop=stop_after_attempt(CRM_FETCH_RECORD_MAX_RETRY_ATTEMPT))
|
||||
def fetch_sf_data_retry(soql: str, target_object_name: str, data_counter: CounterObject):
|
||||
try:
|
||||
salesforce_api_client = SalesforceApiClient()
|
||||
return salesforce_api_client.fetch_sf_data(soql)
|
||||
|
||||
except ConnectTimeout as e:
|
||||
# 「リトライします」のメッセージ出力後、リトライせず例外終了になってしまうことを防ぐため、カウンタによる回数の判定を行う
|
||||
if data_counter.describe() < CRM_FETCH_RECORD_MAX_RETRY_ATTEMPT:
|
||||
data_counter.increment(1)
|
||||
logger.warning(f'W-FETCH-04 CRMの接続処理がタイムアウトしため、リトライします:[{CRM_AUTH_TIMEOUT}] エラー内容:[{e}]')
|
||||
raise e
|
||||
|
||||
except ReadTimeout as e:
|
||||
# 「リトライします」のメッセージ出力後、リトライせず例外終了になってしまうことを防ぐため、カウンタによる回数の判定を行う
|
||||
if data_counter.describe() < CRM_FETCH_RECORD_MAX_RETRY_ATTEMPT:
|
||||
data_counter.increment(1)
|
||||
logger.warning(
|
||||
f'W-FETCH-05 [{target_object_name}] のレコード取得処理がタイムアウトしたため、リトライします:[{CRM_FETCH_RECORD_TIMEOUT}] エラー内容:[{e}]')
|
||||
raise e
|
||||
|
||||
except Exception as e:
|
||||
# 「リトライします」のメッセージ出力後、リトライせず例外終了になってしまうことを防ぐため、カウンタによる回数の判定を行う
|
||||
if data_counter.describe() < CRM_FETCH_RECORD_MAX_RETRY_ATTEMPT:
|
||||
data_counter.increment(1)
|
||||
logger.warning(
|
||||
f'W-FETCH-06 [{target_object_name}] のレコード取得に失敗したため、リトライします エラー内容:[{e}]')
|
||||
raise e
|
||||
0
ecs/crm-datafetch/src/parser/__init__.py
Normal file
0
ecs/crm-datafetch/src/parser/__init__.py
Normal file
17
ecs/crm-datafetch/src/parser/json_parser.py
Normal file
17
ecs/crm-datafetch/src/parser/json_parser.py
Normal file
@ -0,0 +1,17 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
from src.system_var.constants import EXCLUDE_SYMBOL
|
||||
|
||||
|
||||
class JsonParser():
|
||||
def __init__(self, json_str) -> None:
|
||||
self.__json_str = json_str
|
||||
|
||||
def parse(self) -> dict:
|
||||
for symbol in EXCLUDE_SYMBOL:
|
||||
# コメントアウトシンボルを含む部分を置き換える正規表現
|
||||
replace_comment_regex = rf'\s(?!\"){symbol}[\s\S]*?.*'
|
||||
self.__json_str = re.sub(replace_comment_regex, '', self.__json_str)
|
||||
|
||||
return json.loads(self.__json_str)
|
||||
82
ecs/crm-datafetch/src/prepare_data_fetch_process.py
Normal file
82
ecs/crm-datafetch/src/prepare_data_fetch_process.py
Normal file
@ -0,0 +1,82 @@
|
||||
from src.aws.s3 import ConfigBucket
|
||||
from src.config.objects import FetchTargetObjects
|
||||
from src.error.exceptions import FileNotFoundException, InvalidConfigException
|
||||
from src.parser.json_parser import JsonParser
|
||||
from src.system_var.constants import PRE_JP_NAME
|
||||
from src.system_var.environments import (CRM_CONFIG_BUCKET,
|
||||
OBJECT_INFO_FILENAME,
|
||||
OBJECT_INFO_FOLDER)
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
from src.util.logger import logger_instance as logger
|
||||
|
||||
|
||||
def prepare_data_fetch_process():
|
||||
"""データ取得準備処理
|
||||
|
||||
Raises:
|
||||
FileNotFoundException: S3上のファイルが存在しない場合
|
||||
InvalidConfigException: オブジェクト情報定義が不正だった場合
|
||||
|
||||
Returns:
|
||||
fetch_target_objects : CRMオブジェクト情報インスタンス
|
||||
execute_datetime : 実行日時取得インスタンス
|
||||
process_result : 取得処理実行結果辞書オブジェクト
|
||||
"""
|
||||
|
||||
# ① データ取得準備処理の開始ログを出力する
|
||||
logger.info('I-PRE-01 データ取得準備処理を開始します')
|
||||
|
||||
# ② 取得処理開始年月日時分秒を控える
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
logger.info(f'I-PRE-02 データ取得処理開始日時:{execute_datetime}')
|
||||
|
||||
try:
|
||||
# ③ S3 設定ファイル保管用バケットから、CRM_取得オブジェクト情報ファイルを取得する
|
||||
object_info_file_s3_path = f's3://{CRM_CONFIG_BUCKET}/{OBJECT_INFO_FOLDER}/{OBJECT_INFO_FILENAME}'
|
||||
logger.info(
|
||||
f'I-PRE-03 CRM_取得オブジェクト情報ファイルの取得開始します ファイルパス:[{object_info_file_s3_path}]')
|
||||
|
||||
config_bucket = ConfigBucket()
|
||||
object_info_file_str = config_bucket.get_object_info_file()
|
||||
|
||||
logger.debug('D-PRE-04 CRM_取得オブジェクト情報ファイルの取得成功しました')
|
||||
|
||||
except Exception as e:
|
||||
raise FileNotFoundException(
|
||||
'E-PRE-01', PRE_JP_NAME, f'CRM_取得オブジェクト情報ファイルが存在しません ファイル名:[{OBJECT_INFO_FILENAME}] エラー内容:[{e}]')
|
||||
|
||||
try:
|
||||
# ④ CRM_取得オブジェクト情報ファイルをパースし、メモリ上に展開する
|
||||
logger.debug('D-PRE-05 CRM_取得オブジェクト情報ファイルをパースします')
|
||||
|
||||
json_parser = JsonParser(object_info_file_str)
|
||||
object_info_file_dict = json_parser.parse()
|
||||
|
||||
logger.debug('D-PRE-06 CRM_取得オブジェクト情報ファイルのパースに成功しました')
|
||||
|
||||
except Exception as e:
|
||||
raise InvalidConfigException(
|
||||
'E-PRE-02', PRE_JP_NAME, f'CRM_取得オブジェクト情報ファイルのパースに失敗しました エラー内容:[{e}]')
|
||||
|
||||
# ⑤ メモリ上のCRM_取得オブジェクト情報のキーobjectsの形式チェックを行う
|
||||
try:
|
||||
logger.debug('D-PRE-07 CRM_取得オブジェクト情報ファイルの形式チェックを開始します')
|
||||
|
||||
fetch_target_objects = FetchTargetObjects(object_info_file_dict)
|
||||
|
||||
logger.debug('D-PRE-08 CRM_取得オブジェクト情報ファイルの形式チェック 正常終了')
|
||||
|
||||
except Exception as e:
|
||||
raise InvalidConfigException(
|
||||
'E-PRE-03', PRE_JP_NAME, f'CRM_取得オブジェクト情報ファイルの形式チェックに失敗しました ファイル名:[{OBJECT_INFO_FILENAME}] エラー内容:[{e}]')
|
||||
|
||||
# ⑥ 処理結果出力用のマップを初期化
|
||||
process_result = {}
|
||||
|
||||
# ⑦ データ取得準備処理の終了ログを出力する
|
||||
logger.info('I-PRE-09 データ取得準備処理を終了します')
|
||||
|
||||
# ⑧ 次の処理へ移行する
|
||||
return (fetch_target_objects, execute_datetime, process_result)
|
||||
0
ecs/crm-datafetch/src/salesforce/__init__.py
Normal file
0
ecs/crm-datafetch/src/salesforce/__init__.py
Normal file
28
ecs/crm-datafetch/src/salesforce/salesforce_api.py
Normal file
28
ecs/crm-datafetch/src/salesforce/salesforce_api.py
Normal file
@ -0,0 +1,28 @@
|
||||
from simple_salesforce import Salesforce
|
||||
from src.system_var.environments import (CRM_AUTH_DOMAIN, CRM_AUTH_TIMEOUT,
|
||||
CRM_FETCH_RECORD_TIMEOUT,
|
||||
CRM_GET_RECORD_COUNT_TIMEOUT,
|
||||
CRM_USER_NAME, CRM_USER_PASSWORD,
|
||||
CRM_USER_SECURITY_TOKEN)
|
||||
|
||||
|
||||
class SalesforceApiClient():
|
||||
def __init__(self) -> None:
|
||||
self.__sf = Salesforce(username=CRM_USER_NAME, password=CRM_USER_PASSWORD,
|
||||
security_token=CRM_USER_SECURITY_TOKEN,
|
||||
domain=CRM_AUTH_DOMAIN
|
||||
)
|
||||
|
||||
def __query(self, soql, include_deleted=True, conn_timeout=100, read_timeout=300):
|
||||
return self.__sf.query(soql, include_deleted, timeout=(float(conn_timeout), float(read_timeout)))
|
||||
|
||||
def __query_all(self, soql, include_deleted=True, conn_timeout=100, read_timeout=300):
|
||||
return self.__sf.query_all(soql, include_deleted, timeout=(float(conn_timeout), float(read_timeout)))
|
||||
|
||||
def fetch_sf_count(self, soql: str):
|
||||
count_res = self.__query(soql, conn_timeout=CRM_AUTH_TIMEOUT, read_timeout=CRM_GET_RECORD_COUNT_TIMEOUT)
|
||||
return count_res.get('records')[0].get('expr0')
|
||||
|
||||
def fetch_sf_data(self, soql: str):
|
||||
data_res = self.__query_all(soql, conn_timeout=CRM_AUTH_TIMEOUT, read_timeout=CRM_FETCH_RECORD_TIMEOUT)
|
||||
return data_res.get('records')
|
||||
41
ecs/crm-datafetch/src/salesforce/soql_builder.py
Normal file
41
ecs/crm-datafetch/src/salesforce/soql_builder.py
Normal file
@ -0,0 +1,41 @@
|
||||
import textwrap
|
||||
|
||||
from src.config.objects import LastFetchDatetime, TargetObject
|
||||
from src.system_var.environments import FETCH_LIMIT_CLAUSE
|
||||
|
||||
|
||||
class SOQLBuilder:
|
||||
def __init__(self, target_object: TargetObject, last_fetch_datetime: LastFetchDatetime) -> None:
|
||||
self.__SELECT_SOQL = textwrap.dedent("""\
|
||||
SELECT {column_or_expression} FROM {object_name}
|
||||
WHERE {datetime_column} > {last_fetch_datetime_from}
|
||||
AND {datetime_column} <= {last_fetch_datetime_to}
|
||||
{limit_clause}
|
||||
""")
|
||||
self.__target_object = target_object
|
||||
self.__last_fetch_datetime = last_fetch_datetime
|
||||
|
||||
def create_count_soql(self):
|
||||
count_soql = self.__SELECT_SOQL.format(
|
||||
column_or_expression='COUNT(Id)',
|
||||
object_name=self.__target_object.object_name,
|
||||
last_fetch_datetime_from=self.__last_fetch_datetime.last_fetch_datetime_from,
|
||||
last_fetch_datetime_to=self.__last_fetch_datetime.last_fetch_datetime_to,
|
||||
datetime_column=self.__target_object.datetime_column,
|
||||
limit_clause=''
|
||||
)
|
||||
|
||||
return count_soql
|
||||
|
||||
def create_fetch_soql(self):
|
||||
columns = ','.join(self.__target_object.columns)
|
||||
fetch_soql = self.__SELECT_SOQL.format(
|
||||
column_or_expression=columns,
|
||||
object_name=self.__target_object.object_name,
|
||||
last_fetch_datetime_from=self.__last_fetch_datetime.last_fetch_datetime_from,
|
||||
last_fetch_datetime_to=self.__last_fetch_datetime.last_fetch_datetime_to,
|
||||
datetime_column=self.__target_object.datetime_column,
|
||||
limit_clause=FETCH_LIMIT_CLAUSE
|
||||
)
|
||||
|
||||
return fetch_soql
|
||||
69
ecs/crm-datafetch/src/set_datetime_period_process.py
Normal file
69
ecs/crm-datafetch/src/set_datetime_period_process.py
Normal file
@ -0,0 +1,69 @@
|
||||
from src.aws.s3 import ConfigBucket
|
||||
from src.config.objects import LastFetchDatetime, TargetObject
|
||||
from src.error.exceptions import FileNotFoundException, InvalidConfigException
|
||||
from src.parser.json_parser import JsonParser
|
||||
from src.system_var.constants import DATE_JP_NAME
|
||||
from src.system_var.environments import (CRM_CONFIG_BUCKET,
|
||||
LAST_FETCH_DATE_FOLDER)
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
from src.util.logger import logger_instance as logger
|
||||
|
||||
|
||||
def set_datetime_period_process(target_object: TargetObject, execute_datetime: ExecuteDateTime):
|
||||
"""データ取得期間設定処理
|
||||
|
||||
Args:
|
||||
target_object (TargetObject): 取得対象オブジェクト情報インスタンス
|
||||
execute_datetime (ExecuteDateTime): 実行日時取得インスタンス
|
||||
|
||||
Raises:
|
||||
FileNotFoundException: S3上のファイルが存在しない場合
|
||||
InvalidConfigException: オブジェクト情報定義が不正だった場合
|
||||
|
||||
Returns:
|
||||
last_fetch_datetime: データ取得期間設定インスタンス
|
||||
"""
|
||||
|
||||
# ① データ取得期間設定処理の開始ログを出力する
|
||||
logger.info(
|
||||
f'I-DATE-01 [{target_object.object_name}] のデータ取得期間設定処理を開始します')
|
||||
|
||||
try:
|
||||
# ② S3 設定ファイル保管用バケットから、前回取得日時ファイルを取得する
|
||||
logger.info(
|
||||
f'I-DATE-02 前回取得日時ファイルの取得開始します ファイルパス:[s3://{CRM_CONFIG_BUCKET}/{LAST_FETCH_DATE_FOLDER}/{target_object.last_fetch_datetime_file_name}]')
|
||||
|
||||
s3_config_bucket = ConfigBucket()
|
||||
last_fetch_datetime_file_str = s3_config_bucket.get_last_fetch_datetime_file(
|
||||
target_object.last_fetch_datetime_file_name)
|
||||
|
||||
logger.info(f'I-DATE-03 前回取得日時ファイルの取得成功しました')
|
||||
|
||||
except Exception as e:
|
||||
raise FileNotFoundException(
|
||||
'E-DATE-01', DATE_JP_NAME, f'前回取得日時ファイルが存在しません ファイル名:[{target_object.last_fetch_datetime_file_name}] エラー内容:[{e}]')
|
||||
|
||||
try:
|
||||
# ③ 取得した前回取得日時ファイルの形式チェックを行う
|
||||
# ④ データの取得期間を設定する
|
||||
logger.debug(f'D-DATE-04 前回取得日時ファイルの形式チェックを開始します')
|
||||
|
||||
json_parser = JsonParser(last_fetch_datetime_file_str)
|
||||
last_fetch_datetime_file_dict = json_parser.parse()
|
||||
|
||||
last_fetch_datetime = LastFetchDatetime(last_fetch_datetime_file_dict, execute_datetime)
|
||||
|
||||
logger.debug(f'D-DATE-05 前回取得日時ファイルの形式チェック 正常終了')
|
||||
logger.info(
|
||||
f'I-DATE-06 取得範囲 From: [{last_fetch_datetime.last_fetch_datetime_from}] To: [{last_fetch_datetime.last_fetch_datetime_to}]')
|
||||
|
||||
except Exception as e:
|
||||
raise InvalidConfigException(
|
||||
'E-DATE-02', DATE_JP_NAME, f'前回取得日時ファイルの形式チェック処理が失敗しました エラー内容:[{e}]')
|
||||
|
||||
# ⑤ データ取得準備処理の終了ログを出力する
|
||||
logger.info(
|
||||
f'I-DATE-07 [{target_object.object_name}] のデータ取得期間設定処理を終了します')
|
||||
|
||||
# ⑥ 次の処理へ移行する
|
||||
return last_fetch_datetime
|
||||
100
ecs/crm-datafetch/src/system_var/constants.py
Normal file
100
ecs/crm-datafetch/src/system_var/constants.py
Normal file
@ -0,0 +1,100 @@
|
||||
# environments(task settings file)
|
||||
LOG_LEVEL = 'LOG_LEVEL' # ログ出力レベル。DEBUG, INFO, WARNING, ERRORの4つから指定する
|
||||
CRM_AUTH_TIMEOUT = 'CRM_AUTH_TIMEOUT' # CRMへの認証処理のタイムアウト秒数
|
||||
CRM_AUTH_MAX_RETRY_ATTEMPT = 'CRM_AUTH_MAX_RETRY_ATTEMPT' # CRMへの認証処理の最大リトライ試行回数
|
||||
CRM_AUTH_RETRY_INTERVAL = 'CRM_AUTH_RETRY_INTERVAL' # CRMへの認証処理のリトライ時の初回待ち秒数
|
||||
CRM_AUTH_RETRY_MIN_INTERVAL = 'CRM_AUTH_RETRY_MIN_INTERVAL' # CRMへの認証処理のリトライ時の最小待ち秒数
|
||||
CRM_AUTH_RETRY_MAX_INTERVAL = 'CRM_AUTH_RETRY_MAX_INTERVAL' # CRMへの認証処理のリトライ時の最大待ち秒数
|
||||
CRM_GET_RECORD_COUNT_TIMEOUT = 'CRM_GET_RECORD_COUNT_TIMEOUT' # CRMのレコード件数取得処理のタイムアウト秒数
|
||||
CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT = 'CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT' # CRMのレコード件数取得処理の最大リトライ試行回数
|
||||
CRM_GET_RECORD_COUNT_RETRY_INTERVAL = 'CRM_GET_RECORD_COUNT_RETRY_INTERVAL' # CRMのレコード件数取得処理のリトライ時の初回待ち秒数
|
||||
CRM_GET_RECORD_COUNT_RETRY_MIN_INTERVAL = 'CRM_GET_RECORD_COUNT_RETRY_MIN_INTERVAL' # CRMのレコード件数取得処理のリトライ時の最小待ち秒数
|
||||
CRM_GET_RECORD_COUNT_RETRY_MAX_INTERVAL = 'CRM_GET_RECORD_COUNT_RETRY_MAX_INTERVAL' # CRMのレコード件数取得処理のリトライ時の最大待ち秒数
|
||||
CRM_FETCH_RECORD_TIMEOUT = 'CRM_FETCH_RECORD_TIMEOUT' # CRMのレコード取得処理のタイムアウト秒数
|
||||
CRM_FETCH_RECORD_MAX_RETRY_ATTEMPT = 'CRM_FETCH_RECORD_MAX_RETRY_ATTEMPT' # CRMのレコード取得処理の最大リトライ試行回数
|
||||
CRM_FETCH_RECORD_RETRY_INTERVAL = 'CRM_FETCH_RECORD_RETRY_INTERVAL' # CRMのレコード取得処理のリトライ時の初回待ち秒数
|
||||
CRM_FETCH_RECORD_RETRY_MIN_INTERVAL = 'CRM_FETCH_RECORD_RETRY_MIN_INTERVAL' # CRMのレコード取得処理のリトライ時の最小待ち秒数
|
||||
CRM_FETCH_RECORD_RETRY_MAX_INTERVAL = 'CRM_FETCH_RECORD_RETRY_MAX_INTERVAL' # CRMのレコード取得処理のリトライ時の最大待ち秒数
|
||||
CONVERT_TZ = 'CONVERT_TZ' # CRMデータの日時を変換するときのタイムゾーン
|
||||
|
||||
# environments(ECS Task Environment)
|
||||
CRM_AUTH_DOMAIN = 'CRM_AUTH_DOMAIN' # CRMのAPI実行のための認証エンドポイントのドメイン
|
||||
CRM_USER_NAME = 'CRM_USER_NAME' # CRMのAPI実行用ユーザ名
|
||||
CRM_USER_PASSWORD = 'CRM_USER_PASSWORD' # CRMのAPI実行用ユーザパスワード
|
||||
CRM_USER_SECURITY_TOKEN = 'CRM_USER_SECURITY_TOKEN' # CRMのAPI実行用ユーザのセキュリティトークン
|
||||
CRM_CONFIG_BUCKET = 'CRM_CONFIG_BUCKET' # CRMデータ取得用の設定ファイルを格納するバケット名
|
||||
CRM_BACKUP_BUCKET = 'CRM_BACKUP_BUCKET' # CRMのバックアップデータを格納するバケット名
|
||||
IMPORT_DATA_BUCKET = 'IMPORT_DATA_BUCKET' # CRMの取込データを格納するバケット名
|
||||
OBJECT_INFO_FOLDER = 'OBJECT_INFO_FOLDER' # CRM取得対象オブジェクトの情報を格納するフォルダパス
|
||||
OBJECT_INFO_FILENAME = 'OBJECT_INFO_FILENAME' # CRM取得対象オブジェクトの情報のファイル名
|
||||
PROCESS_RESULT_FOLDER = 'PROCESS_RESULT_FOLDER' # CRMデータ取得結果を格納するフォルダパス
|
||||
PROCESS_RESULT_FILENAME = 'PROCESS_RESULT_FILENAME' # CRMデータ取得結果を格納するファイル名
|
||||
LAST_FETCH_DATE_FOLDER = 'LAST_FETCH_DATE_FOLDER' # CRMからの最終取得日時ファイルを格納するフォルダパス
|
||||
CRM_IMPORT_DATA_FOLDER = 'CRM_IMPORT_DATA_FOLDER' # CRMから取得し、取込用に変換したデータを格納するフォルダ
|
||||
LAST_FETCH_DATE_BACKUP_FOLDER = 'LAST_FETCH_DATE_BACKUP_FOLDER' # CRMからの最終取得日時ファイルのバックアップを格納するフォルダパス
|
||||
RESPONSE_JSON_BACKUP_FOLDER = 'RESPONSE_JSON_BACKUP_FOLDER' # CRMから取得した生データのバックアップを格納するフォルダパス
|
||||
CRM_IMPORT_DATA_BACKUP_FOLDER = 'CRM_IMPORT_DATA_BACKUP_FOLDER' # CRMから取得し、取込用に変換したデータのバックアップを格納するフォルダ
|
||||
|
||||
# 時刻フォーマット
|
||||
# .000ZはUTCを表す。ミリ秒までの考慮は不要なので固定で指定
|
||||
YYYYMMDDTHHMMSSTZ = '%Y-%m-%dT%H:%M:%S.000Z'
|
||||
CRM_DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.000%z'
|
||||
YYYYMMDDHHMMSS = '%Y-%m-%d %H:%M:%S'
|
||||
MILLISEC_FORMAT = '000Z'
|
||||
|
||||
# aws
|
||||
AWS_RESOURCE_S3 = 's3'
|
||||
S3_RESPONSE_BODY = 'Body'
|
||||
S3_CHAR_CODE = 'utf-8'
|
||||
|
||||
# 正規表現チェック
|
||||
EXCLUDE_SYMBOL = ['#', '/']
|
||||
DATE_PATTERN_YYYYMMDDTHHMMSSTZ = r'[12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\.000Z'
|
||||
DATE_PATTERN_EXPECTED_YYYYMMDDTHHMMSSTZ = 'YYYY-MM-DDTHH:MM:SS.000Z'
|
||||
DATE_PATTERN_YYYYMMDDHHMMSSFFF_UTC = r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.000\+0000'
|
||||
|
||||
|
||||
# logger
|
||||
LOG_FORMAT = '[%(levelname)s]\t%(asctime)s\t%(message)s\n'
|
||||
LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
|
||||
LOG_LEVEL_INFO = 'INFO'
|
||||
|
||||
# 処理名
|
||||
PROCESS_JP_NAME = 'コントロール処理'
|
||||
PRE_JP_NAME = 'データ取得準備処理'
|
||||
CHK_JP_NAME = 'オブジェクト情報形式チェック処理'
|
||||
DATE_JP_NAME = 'データ取得期間設定処理'
|
||||
FETCH_JP_NAME = 'CRMデータ取得処理'
|
||||
RESBK_JP_NAME = 'CRM電文データバックアップ処理'
|
||||
CONV_JP_NAME = 'CSV変換処理'
|
||||
CSVBK_JP_NAME = 'CSVバックアップ処理'
|
||||
UPLD_JP_NAME = 'CSVアップロード処理'
|
||||
UPD_JP_NAME = '前回取得日時ファイル更新'
|
||||
END_JP_NAME = '取得処理実施結果アップロード処理'
|
||||
|
||||
# CSVチェック
|
||||
CSV_TRUE_VALUE = 1
|
||||
CSV_FALSE_VALUE = 0
|
||||
|
||||
# オブジェクト変数
|
||||
OBJECTS_KEY = 'objects'
|
||||
OBJECTS_TYPE = list
|
||||
OBJECT_NAME_KEY = 'object_name'
|
||||
OBJECT_NAME_TYPE = str
|
||||
COLUMNS_KEY = 'columns'
|
||||
COLUMNS_TYPE = list
|
||||
IS_SKIP_KEY = 'is_skip'
|
||||
IS_SKIP_TYPE = bool
|
||||
IS_UPDATE_LAST_FETCH_DATETIME_KEY = 'is_update_last_fetch_datetime'
|
||||
IS_UPDATE_LAST_FETCH_DATETIME_TYPE = bool
|
||||
LAST_FETCH_DATETIME_FILE_NAME_KEY = 'last_fetch_datetime_file_name'
|
||||
LAST_FETCH_DATETIME_FILE_NAME_TYPE = str
|
||||
UPLOAD_FILE_NAME_KEY = 'upload_file_name'
|
||||
UPLOAD_FILE_NAME_TYPE = str
|
||||
DATETIME_COLUMN_KEY = 'datetime_column'
|
||||
DATETIME_COLUMN_TYPE = str
|
||||
DATETIME_COLUMN_DEFAULT_VALUE = 'SystemModstamp'
|
||||
LAST_FETCH_DATETIME_TO_KEY = 'last_fetch_datetime_to'
|
||||
LAST_FETCH_DATETIME_TO_TYPE = str
|
||||
LAST_FETCH_DATETIME_FROM_KEY = 'last_fetch_datetime_from'
|
||||
LAST_FETCH_DATETIME_FROM_TYPE = str
|
||||
65
ecs/crm-datafetch/src/system_var/environments.py
Normal file
65
ecs/crm-datafetch/src/system_var/environments.py
Normal file
@ -0,0 +1,65 @@
|
||||
import os
|
||||
|
||||
import src.system_var.constants as constants
|
||||
|
||||
# environments(task settings file)
|
||||
# ログ出力レベル。DEBUG, INFO, WARNING, ERRORの4つから指定する
|
||||
LOG_LEVEL = os.environ.get(constants.LOG_LEVEL, constants.LOG_LEVEL_INFO)
|
||||
# CRMへの認証処理のタイムアウト秒数
|
||||
CRM_AUTH_TIMEOUT = int(os.environ.get(constants.CRM_AUTH_TIMEOUT, 100))
|
||||
# CRMのレコード件数取得処理のタイムアウト秒数
|
||||
CRM_GET_RECORD_COUNT_TIMEOUT = int(os.environ.get(constants.CRM_GET_RECORD_COUNT_TIMEOUT, 300))
|
||||
# CRMのレコード件数取得処理の最大リトライ試行回数(処理の実施総回数)
|
||||
CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT = int(os.environ.get(constants.CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT, 4))
|
||||
# CRMのレコード件数取得処理のリトライ時の初回待ち秒数
|
||||
CRM_GET_RECORD_COUNT_RETRY_INTERVAL = int(os.environ.get(constants.CRM_GET_RECORD_COUNT_RETRY_INTERVAL, 5))
|
||||
# CRMのレコード件数取得処理のリトライ時の最小待ち秒数
|
||||
CRM_GET_RECORD_COUNT_RETRY_MIN_INTERVAL = int(os.environ.get(constants.CRM_GET_RECORD_COUNT_RETRY_MIN_INTERVAL, 5))
|
||||
# CRMのレコード件数取得処理のリトライ時の最大待ち秒数
|
||||
CRM_GET_RECORD_COUNT_RETRY_MAX_INTERVAL = int(os.environ.get(constants.CRM_GET_RECORD_COUNT_RETRY_MAX_INTERVAL, 50))
|
||||
# CRMのレコード取得処理のタイムアウト秒数
|
||||
CRM_FETCH_RECORD_TIMEOUT = int(os.environ.get(constants.CRM_FETCH_RECORD_TIMEOUT, 300))
|
||||
# CRMのレコード取得処理の最大リトライ試行回数(処理の実施総回数)
|
||||
CRM_FETCH_RECORD_MAX_RETRY_ATTEMPT = int(os.environ.get(constants.CRM_FETCH_RECORD_MAX_RETRY_ATTEMPT, 4))
|
||||
# CRMのレコード取得処理のリトライ時の初回待ち秒数
|
||||
CRM_FETCH_RECORD_RETRY_INTERVAL = int(os.environ.get(constants.CRM_FETCH_RECORD_RETRY_INTERVAL, 5))
|
||||
# CRMのレコード取得処理のリトライ時の最小待ち秒数
|
||||
CRM_FETCH_RECORD_RETRY_MIN_INTERVAL = int(os.environ.get(constants.CRM_FETCH_RECORD_RETRY_MIN_INTERVAL, 5))
|
||||
# CRMのレコード取得処理のリトライ時の最大待ち秒数
|
||||
CRM_FETCH_RECORD_RETRY_MAX_INTERVAL = int(os.environ.get(constants.CRM_FETCH_RECORD_RETRY_MAX_INTERVAL, 50))
|
||||
# CRMデータの日時を変換するときのタイムゾーン
|
||||
CONVERT_TZ = os.environ.get(constants.CONVERT_TZ, 'Asia/Tokyo')
|
||||
|
||||
# environments(ECS Task Environment)
|
||||
# CRMのAPI実行のための認証エンドポイントのドメイン
|
||||
CRM_AUTH_DOMAIN = os.environ[constants.CRM_AUTH_DOMAIN]
|
||||
# CRMのAPI実行用ユーザ名
|
||||
CRM_USER_NAME = os.environ[constants.CRM_USER_NAME]
|
||||
# CRMのAPI実行用ユーザパスワード
|
||||
CRM_USER_PASSWORD = os.environ[constants.CRM_USER_PASSWORD]
|
||||
# CRMのAPI実行用ユーザのセキュリティトークン
|
||||
CRM_USER_SECURITY_TOKEN = os.environ[constants.CRM_USER_SECURITY_TOKEN]
|
||||
# CRMデータ取得用の設定ファイルを格納するバケット名
|
||||
CRM_CONFIG_BUCKET = os.environ[constants.CRM_CONFIG_BUCKET]
|
||||
# CRMのバックアップデータを格納するバケット名
|
||||
CRM_BACKUP_BUCKET = os.environ[constants.CRM_BACKUP_BUCKET]
|
||||
# CRMの取込データを格納するバケット名
|
||||
IMPORT_DATA_BUCKET = os.environ[constants.IMPORT_DATA_BUCKET]
|
||||
# CRM取得対象オブジェクトの情報を格納するフォルダパス
|
||||
OBJECT_INFO_FOLDER = os.environ.get(constants.OBJECT_INFO_FOLDER, 'crm/object_info')
|
||||
# CRM取得対象オブジェクトの情報のファイル名
|
||||
OBJECT_INFO_FILENAME = os.environ[constants.OBJECT_INFO_FILENAME]
|
||||
# CRMデータ取得結果を格納するフォルダパス
|
||||
PROCESS_RESULT_FOLDER = os.environ.get(constants.PROCESS_RESULT_FOLDER, 'data_import')
|
||||
# CRMデータ取得結果を格納するファイル名
|
||||
PROCESS_RESULT_FILENAME = os.environ.get(constants.PROCESS_RESULT_FILENAME, 'process_result.json')
|
||||
# CRMからの最終取得日時ファイルを格納するフォルダパス
|
||||
LAST_FETCH_DATE_FOLDER = os.environ.get(constants.LAST_FETCH_DATE_FOLDER, 'crm/last_fetch_datetime')
|
||||
# CRMから取得し、取込用に変換したデータを格納するフォルダ
|
||||
CRM_IMPORT_DATA_FOLDER = os.environ.get(constants.CRM_IMPORT_DATA_FOLDER, 'crm/target')
|
||||
# CRMから取得した生データのバックアップを格納するフォルダパス
|
||||
RESPONSE_JSON_BACKUP_FOLDER = os.environ.get(constants.RESPONSE_JSON_BACKUP_FOLDER, 'response_json')
|
||||
# CRMから取得し、取込用に変換したデータのバックアップを格納するフォルダ
|
||||
CRM_IMPORT_DATA_BACKUP_FOLDER = os.environ.get(constants.CRM_IMPORT_DATA_BACKUP_FOLDER, 'data_import')
|
||||
# 一気通貫テスト用、取得件数を絞るためのSOQL句
|
||||
FETCH_LIMIT_CLAUSE = os.environ.get('FETCH_LIMIT_CLAUSE', '')
|
||||
57
ecs/crm-datafetch/src/upload_last_fetch_datetime_process.py
Normal file
57
ecs/crm-datafetch/src/upload_last_fetch_datetime_process.py
Normal file
@ -0,0 +1,57 @@
|
||||
import json
|
||||
|
||||
from src.aws.s3 import ConfigBucket
|
||||
from src.config.objects import LastFetchDatetime, TargetObject
|
||||
from src.error.exceptions import FileUploadException
|
||||
from src.system_var.constants import UPD_JP_NAME
|
||||
from src.util.logger import logger_instance as logger
|
||||
|
||||
|
||||
def upload_last_fetch_datetime_process(target_object: TargetObject, last_fetch_datetime: LastFetchDatetime):
|
||||
"""前回取得日時ファイル更新
|
||||
|
||||
Args:
|
||||
target_object (TargetObject): 取得対象オブジェクト情報インスタンス
|
||||
last_fetch_datetime (LastFetchDatetime): データ取得期間設定インスタンス
|
||||
|
||||
Raises:
|
||||
FileUploadException: S3のファイルアップロード失敗
|
||||
"""
|
||||
|
||||
# ① 前回取得日時ファイル更新処理の開始ログを出力する
|
||||
logger.info(
|
||||
f'I-UPD-01 [{target_object.object_name}] の前回取得日時ファイルの更新処理を開始します')
|
||||
|
||||
try:
|
||||
if target_object.is_update_last_fetch_datetime is False:
|
||||
# ② オブジェクト情報.is_update_last_fetch_datetimeがfalseの場合、以降の処理をスキップする
|
||||
logger.info(
|
||||
f'I-UPD-02 [{target_object.object_name}] の前回取得日時ファイルの更新処理をスキップします')
|
||||
return
|
||||
|
||||
# ③ 前回取得日時ファイル.last_fetch_datetime_fromに取得処理開始年月日時分秒を設定する
|
||||
# 前回取得日時ファイル.last_fetch_datetime_toに空文字を設定する
|
||||
last_fetch_datetime_dict = {
|
||||
'last_fetch_datetime_from': last_fetch_datetime.last_fetch_datetime_to,
|
||||
'last_fetch_datetime_to': ''
|
||||
}
|
||||
|
||||
config_bucket = ConfigBucket()
|
||||
config_bucket.put_last_fetch_datetime_file(
|
||||
target_object.last_fetch_datetime_file_name, json.dumps(last_fetch_datetime_dict))
|
||||
|
||||
logger.info(
|
||||
f'D-UPD-03 [{target_object.object_name}] の前回取得日時ファイル更新処理 正常終了')
|
||||
|
||||
except Exception as e:
|
||||
raise FileUploadException(
|
||||
'E-UPD-01',
|
||||
UPD_JP_NAME,
|
||||
f'[{target_object.object_name}] 前回処理日時ファイルのアップロードに失敗しました ファイル名:[{target_object.last_fetch_datetime_file_name}] エラー内容:[{e}]')
|
||||
|
||||
# ④ 前回取得日時ファイル更新処理の終了ログを出力する
|
||||
logger.info(
|
||||
f'I-UPD-04 [{target_object.object_name}] の前回取得日時ファイルの更新処理を終了します')
|
||||
|
||||
# ⑤ 次の処理へ移行する
|
||||
return
|
||||
39
ecs/crm-datafetch/src/upload_result_data_process.py
Normal file
39
ecs/crm-datafetch/src/upload_result_data_process.py
Normal file
@ -0,0 +1,39 @@
|
||||
from src.aws.s3 import BackupBucket
|
||||
from src.error.exceptions import FileUploadException
|
||||
from src.system_var.constants import END_JP_NAME
|
||||
from src.system_var.environments import PROCESS_RESULT_FILENAME
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
from src.util.logger import logger_instance as logger
|
||||
|
||||
|
||||
def upload_result_data_process(process_result: dict, execute_datetime: ExecuteDateTime):
|
||||
"""取得処理実施結果アップロード処理
|
||||
|
||||
Args:
|
||||
process_result (dict): 取得処理実行結果辞書オブジェクト
|
||||
execute_datetime (ExecuteDateTime): 実行日時取得インスタンス
|
||||
|
||||
Raises:
|
||||
FileUploadException: S3のファイルアップロード失敗
|
||||
"""
|
||||
|
||||
# ① 取得処理実施結果アップロード処理のログを出力する
|
||||
logger.info(
|
||||
f'I-END-01 取得処理実施結果アップロード処理を開始します')
|
||||
|
||||
try:
|
||||
# ② CRMバックアップ保管用バケットに、取得処理実施結果のJSONデータを保管する
|
||||
backup_bucket = BackupBucket()
|
||||
backup_bucket.put_result_json(
|
||||
f'{execute_datetime.to_path()}/{PROCESS_RESULT_FILENAME}', process_result)
|
||||
|
||||
logger.debug(f'D-END-02 取得処理実施結果アップロード 正常終了')
|
||||
|
||||
except Exception as e:
|
||||
raise FileUploadException(
|
||||
'E-END-01', END_JP_NAME, f'取得処理実施結果のアップロードに失敗しました ファイル名:[{PROCESS_RESULT_FILENAME}] エラー内容:[{e}]')
|
||||
|
||||
# ③ 取得処理実施結果アップロード処理の終了ログを出力する
|
||||
logger.info(f'I-END-03 取得処理実施結果アップロード処理を終了します')
|
||||
|
||||
return
|
||||
0
ecs/crm-datafetch/src/util/__init__.py
Normal file
0
ecs/crm-datafetch/src/util/__init__.py
Normal file
14
ecs/crm-datafetch/src/util/counter_object.py
Normal file
14
ecs/crm-datafetch/src/util/counter_object.py
Normal file
@ -0,0 +1,14 @@
|
||||
class CounterObject:
|
||||
def __init__(self, base_num=1) -> None:
|
||||
self.__counter = int(base_num)
|
||||
|
||||
def describe(self) -> int:
|
||||
return self.__counter
|
||||
|
||||
def increment(self, num=1) -> int:
|
||||
self.__counter += num
|
||||
return self.__counter
|
||||
|
||||
def decrement(self, num=1) -> int:
|
||||
self.__counter -= num
|
||||
return self.__counter
|
||||
51
ecs/crm-datafetch/src/util/dict_checker.py
Normal file
51
ecs/crm-datafetch/src/util/dict_checker.py
Normal file
@ -0,0 +1,51 @@
|
||||
import re
|
||||
|
||||
|
||||
class DictChecker:
|
||||
def __init__(self, object_dict: dict) -> None:
|
||||
self.__object_dict = object_dict
|
||||
|
||||
def is_empty(self, check_key):
|
||||
"""辞書型バリュー空文字チェック"""
|
||||
return self.__object_dict[check_key] == '' or self.__object_dict[check_key] is None
|
||||
|
||||
def is_list_empty(self, check_key):
|
||||
"""list型データ存在チェック"""
|
||||
return len(self.__object_dict[check_key]) == 0
|
||||
|
||||
def check_key_exist(self, check_key: str) -> bool:
|
||||
"""辞書型キー存在チェック"""
|
||||
return check_key in self.__object_dict and not self.is_empty(check_key)
|
||||
|
||||
def check_data_type(self, check_key: str, check_type: type) -> bool:
|
||||
"""辞書型バリュー型チェック"""
|
||||
return isinstance(self.__object_dict[check_key], check_type)
|
||||
|
||||
def check_match_pattern(self, regex_str: str, check_key: str) -> bool:
|
||||
"""辞書型バリュー正規表現チェック"""
|
||||
return True if re.fullmatch(regex_str, self.__object_dict[check_key]) else False
|
||||
|
||||
def assert_key_exist(self, check_key: str) -> None:
|
||||
"""辞書型キー存在検査"""
|
||||
if not self.check_key_exist(check_key):
|
||||
raise Exception(f'「{check_key}」キーは必須です')
|
||||
|
||||
return
|
||||
|
||||
def assert_data_type(self, check_key: str, check_type: type) -> None:
|
||||
"""バリュー型検査"""
|
||||
if not self.check_data_type(check_key, check_type):
|
||||
raise Exception(f'「{check_key}」キーの値は「{check_type}」でなければなりません')
|
||||
|
||||
return
|
||||
|
||||
def assert_match_pattern(self, check_key: str, regex_str: str, expected_str: str):
|
||||
"""正規表現検査"""
|
||||
if not self.check_match_pattern(regex_str, check_key):
|
||||
raise Exception(f'「{check_key}」キーの値の正規表現チェックに失敗しました 「{expected_str}」形式である必要があります')
|
||||
|
||||
return
|
||||
|
||||
def assert_list_empty(self, check_key: str):
|
||||
if self.is_list_empty(check_key):
|
||||
raise Exception(f'「{check_key}」キーのリストの値は必須です')
|
||||
17
ecs/crm-datafetch/src/util/execute_datetime.py
Normal file
17
ecs/crm-datafetch/src/util/execute_datetime.py
Normal file
@ -0,0 +1,17 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from src.system_var.constants import MILLISEC_FORMAT, YYYYMMDDTHHMMSSTZ
|
||||
|
||||
|
||||
class ExecuteDateTime:
|
||||
def __init__(self):
|
||||
self.__execute_datetime = datetime.now(timezone.utc).strftime(YYYYMMDDTHHMMSSTZ)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__execute_datetime
|
||||
|
||||
def to_path(self) -> str:
|
||||
return self.__execute_datetime.rstrip(MILLISEC_FORMAT).translate(str.maketrans({'-': '/', 'T': '/', ':': None, '.': None}))
|
||||
|
||||
def format_date(self) -> str:
|
||||
return self.__execute_datetime.rstrip(MILLISEC_FORMAT).translate(str.maketrans({'-': None, 'T': None, ':': None, '.': None}))
|
||||
37
ecs/crm-datafetch/src/util/logger.py
Normal file
37
ecs/crm-datafetch/src/util/logger.py
Normal file
@ -0,0 +1,37 @@
|
||||
import logging
|
||||
|
||||
from src.system_var.environments import LOG_LEVEL
|
||||
|
||||
# boto3関連モジュールのログレベルを事前に個別指定し、モジュール内のDEBUGログの表示を抑止する
|
||||
for name in ["boto3", "botocore", "s3transfer", "urllib3"]:
|
||||
logging.getLogger(name).setLevel(logging.WARNING)
|
||||
|
||||
|
||||
class Logger():
|
||||
__logger: logging.Logger
|
||||
|
||||
def __init__(self):
|
||||
self.__logger = logging.getLogger()
|
||||
|
||||
level = logging.getLevelName(LOG_LEVEL)
|
||||
if not isinstance(level, int):
|
||||
level = logging.INFO
|
||||
self.__logger.setLevel(level)
|
||||
|
||||
if not self.__logger.hasHandlers():
|
||||
handler = logging.StreamHandler()
|
||||
self.__logger.addHandler(handler)
|
||||
|
||||
formatter = logging.Formatter(
|
||||
'[%(levelname)s]\t%(asctime)s\t%(message)s\n',
|
||||
'%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
for handler in self.__logger.handlers:
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
def get_logger(self) -> logging.Logger:
|
||||
return self.__logger
|
||||
|
||||
|
||||
logger_instance = Logger().get_logger()
|
||||
0
ecs/crm-datafetch/tests/__init__.py
Normal file
0
ecs/crm-datafetch/tests/__init__.py
Normal file
0
ecs/crm-datafetch/tests/aws/__init__.py
Normal file
0
ecs/crm-datafetch/tests/aws/__init__.py
Normal file
487
ecs/crm-datafetch/tests/aws/test_s3.py
Normal file
487
ecs/crm-datafetch/tests/aws/test_s3.py
Normal file
@ -0,0 +1,487 @@
|
||||
import pytest
|
||||
from src.aws.s3 import BackupBucket, ConfigBucket, DataBucket, S3Resource
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bucket_name():
|
||||
return 'test-bucket'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def s3_test(s3_client, bucket_name):
|
||||
s3_client.create_bucket(Bucket=bucket_name)
|
||||
yield
|
||||
|
||||
|
||||
class TestS3Resource:
|
||||
|
||||
def test_get_object(self, s3_test, s3_client, bucket_name):
|
||||
"""
|
||||
Cases:
|
||||
- S3からオブジェクトが取得できるか
|
||||
Arranges:
|
||||
- S3をモック化する
|
||||
- 期待値となるファイルを配置する
|
||||
Expects:
|
||||
- オブジェクトが取得でき、期待値と正しいこと
|
||||
"""
|
||||
# Arrange
|
||||
s3_client.put_object(Bucket=bucket_name, Key='hogehoge/test.txt', Body=b'aaaaaaaaaaaaaaa')
|
||||
|
||||
# Act
|
||||
sut = S3Resource(bucket_name)
|
||||
actual = sut.get_object('hogehoge/test.txt')
|
||||
|
||||
# Assert
|
||||
assert actual == 'aaaaaaaaaaaaaaa'
|
||||
|
||||
def test_raise_get_object(self, s3_test, s3_client, bucket_name):
|
||||
"""
|
||||
Cases:
|
||||
- オブジェクトのキーが存在しない場合エラーになること
|
||||
Arranges:
|
||||
- S3をモック化する
|
||||
Expects:
|
||||
- オブジェクト取得時にエラーになること
|
||||
"""
|
||||
# Arrange
|
||||
# Act
|
||||
sut = S3Resource(bucket_name)
|
||||
with pytest.raises(Exception):
|
||||
# Assert
|
||||
sut.get_object('hogehoge/test.txt')
|
||||
|
||||
def test_put_object(self, s3_test, s3_client, bucket_name):
|
||||
"""
|
||||
Cases:
|
||||
- S3にオブジェクトをPUTできるか
|
||||
Arranges:
|
||||
- S3をモック化する
|
||||
Expects:
|
||||
- PUTされたファイルが存在する
|
||||
- PUTされたファイルの内容が期待値と一致する
|
||||
"""
|
||||
sut = S3Resource(bucket_name)
|
||||
|
||||
sut.put_object('hogehoge/test.txt', 'aaaaaaaaaaaaaaa')
|
||||
actual = s3_client.get_object(Bucket=bucket_name, Key='hogehoge/test.txt')
|
||||
|
||||
assert actual['Body'].read().decode('utf-8') == 'aaaaaaaaaaaaaaa'
|
||||
|
||||
def test_raise_put_object(self, s3_client, bucket_name):
|
||||
"""
|
||||
Cases:
|
||||
- バケットが存在しない場合エラーになること
|
||||
Arranges:
|
||||
- S3をモック化する
|
||||
- バケットを作成しない
|
||||
Expects:
|
||||
- オブジェクト登録時にエラーになること
|
||||
"""
|
||||
# Arrange
|
||||
# Act
|
||||
sut = S3Resource(bucket_name)
|
||||
with pytest.raises(Exception):
|
||||
# Assert
|
||||
sut.put_object('hogehoge/test.txt', 'aaaaaaaaaaaaaaa')
|
||||
|
||||
def test_copy(self, s3_test, s3_client, bucket_name):
|
||||
"""
|
||||
Cases:
|
||||
- S3内のオブジェクトを別バケットにコピーできるか
|
||||
Arranges:
|
||||
- S3をモック化する
|
||||
- コピー先バケットを作成する
|
||||
- 期待値となるファイルをコピー元バケットに配置する
|
||||
Expects:
|
||||
- コピーされたファイルが存在する
|
||||
- コピーされたファイルの内容が期待値と一致する
|
||||
"""
|
||||
for_copy_bucket = 'for-copy-bucket'
|
||||
s3_client.create_bucket(Bucket=for_copy_bucket)
|
||||
s3_client.put_object(Bucket=bucket_name, Key='hogehoge/test.txt', Body=b'aaaaaaaaaaaaaaa')
|
||||
|
||||
sut = S3Resource(bucket_name)
|
||||
sut.copy(bucket_name, 'hogehoge/test.txt', for_copy_bucket, 'test.txt')
|
||||
|
||||
actual = s3_client.get_object(Bucket=for_copy_bucket, Key='test.txt')
|
||||
assert actual['Body'].read() == b'aaaaaaaaaaaaaaa'
|
||||
|
||||
def test_raise_copy(self, s3_client, s3_test, bucket_name):
|
||||
"""
|
||||
Cases:
|
||||
- コピー対象のキーが存在しない場合エラーになること
|
||||
Arranges:
|
||||
- S3をモック化する
|
||||
Expects:
|
||||
- オブジェクト登録時にエラーになること
|
||||
"""
|
||||
# Arrange
|
||||
# Act
|
||||
sut = S3Resource(bucket_name)
|
||||
with pytest.raises(Exception):
|
||||
# Assert
|
||||
sut.copy(bucket_name, 'hogehoge/test.txt', 'for_copy_bucket', 'test.txt')
|
||||
|
||||
def test_raise_init_no_provide_bucket_name(self):
|
||||
"""
|
||||
Cases:
|
||||
- バケット名を指定しない場合、例外となること
|
||||
Arranges:
|
||||
|
||||
Expects:
|
||||
- インスタンス生成時に例外が発生すること
|
||||
"""
|
||||
with pytest.raises(Exception) as e:
|
||||
S3Resource()
|
||||
assert e.value.args[0] == "__init__() missing 1 required positional argument: 'bucket_name'"
|
||||
|
||||
|
||||
class TestConfigBucket:
|
||||
|
||||
def test_get_object_info_file(self, s3_test, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- オブジェクト情報ファイルが取得できること
|
||||
Arranges:
|
||||
- 環境変数を置き換える
|
||||
- オブジェクト情報ファイルを配置する
|
||||
Expects:
|
||||
- オブジェクト情報ファイルが文字列として取得でき、期待値と一致する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.CRM_CONFIG_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.OBJECT_INFO_FOLDER', 'crm')
|
||||
monkeypatch.setattr('src.aws.s3.OBJECT_INFO_FILENAME', 'objects.json')
|
||||
s3_client.put_object(Bucket=bucket_name, Key=f'crm/objects.json', Body=b'aaaaaaaaaaaaaaa')
|
||||
|
||||
sut = ConfigBucket()
|
||||
actual = sut.get_object_info_file()
|
||||
|
||||
assert actual == 'aaaaaaaaaaaaaaa'
|
||||
|
||||
def test_raise_get_object_info_file(self, s3_test, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- オブジェクト情報ファイルが存在しない場合、エラーになること
|
||||
Arranges:
|
||||
- s3_testフィクスチャを引数に指定しない
|
||||
- 環境変数を置き換える
|
||||
Expects:
|
||||
- オブジェクト情報ファイルが取得できず、例外が発生する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.CRM_CONFIG_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.OBJECT_INFO_FOLDER', 'crm')
|
||||
monkeypatch.setattr('src.aws.s3.OBJECT_INFO_FILENAME', 'objects.json')
|
||||
|
||||
sut = ConfigBucket()
|
||||
with pytest.raises(Exception):
|
||||
sut.get_object_info_file()
|
||||
|
||||
def test_get_last_fetch_datetime_file(self, s3_test, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- オブジェクト最終更新日時ファイルが取得できること
|
||||
Arranges:
|
||||
- S3をモック化する
|
||||
- オブジェクト最終更新日時ファイルを配置する
|
||||
Expects:
|
||||
- オブジェクト最終更新日時ファイルが文字列として取得でき、期待値と一致する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.CRM_CONFIG_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.LAST_FETCH_DATE_FOLDER', 'crm')
|
||||
s3_client.put_object(Bucket=bucket_name, Key=f'crm/Object.json', Body=b'aaaaaaaaaaaaaaa')
|
||||
|
||||
sut = ConfigBucket()
|
||||
actual = sut.get_last_fetch_datetime_file('Object.json')
|
||||
|
||||
assert actual == 'aaaaaaaaaaaaaaa'
|
||||
|
||||
def test_raise_get_last_fetch_datetime_file(self, s3_test, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- オブジェクト最終更新日時ファイルが存在しない場合、エラーになること
|
||||
Arranges:
|
||||
- s3_testフィクスチャを引数に指定しない
|
||||
- 環境変数を置き換える
|
||||
Expects:
|
||||
- オブジェクト最終更新日時ファイルが取得できず、例外が発生する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.CRM_CONFIG_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.LAST_FETCH_DATE_FOLDER', 'crm')
|
||||
|
||||
sut = ConfigBucket()
|
||||
with pytest.raises(Exception):
|
||||
sut.get_last_fetch_datetime_file('Object.json')
|
||||
|
||||
def test_put_last_fetch_datetime_file(self, s3_test, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- オブジェクト最終更新日時ファイルをPUTできること
|
||||
Arranges:
|
||||
- S3をモック化する
|
||||
- 環境変数をテスト用の値に置き換える
|
||||
Expects:
|
||||
- オブジェクト最終更新日時ファイルが存在する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.CRM_CONFIG_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.LAST_FETCH_DATE_FOLDER', 'crm')
|
||||
|
||||
sut = ConfigBucket()
|
||||
sut.put_last_fetch_datetime_file('Object.json', 'aaaaaaaaaaaaaaa')
|
||||
|
||||
actual = s3_client.get_object(Bucket=bucket_name, Key=f'crm/Object.json')
|
||||
assert actual['Body'].read().decode('utf-8') == 'aaaaaaaaaaaaaaa'
|
||||
|
||||
def test_raise_put_last_fetch_datetime_file(self, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- オブジェクト最終更新日時ファイルをPUTできない場合、エラーになること
|
||||
Arranges:
|
||||
- s3_testフィクスチャを引数に指定しない
|
||||
- 環境変数を置き換える
|
||||
Expects:
|
||||
- オブジェクト最終更新日時ファイルをPUTできず、例外が発生する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.CRM_CONFIG_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.LAST_FETCH_DATE_FOLDER', 'crm')
|
||||
|
||||
sut = ConfigBucket()
|
||||
with pytest.raises(Exception):
|
||||
sut.put_last_fetch_datetime_file('Object.json', 'aaaaaaaaaaaaaaa')
|
||||
|
||||
def test_config_bucket_str(self, s3_test, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- 設定ファイル配置バケットを文字列化したとき、バケット名が取得できること
|
||||
Arranges:
|
||||
- 環境変数をテスト用の値に置き換える
|
||||
Expects:
|
||||
- 環境変数の設定ファイル配置バケット名と一致する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.CRM_CONFIG_BUCKET', bucket_name)
|
||||
|
||||
sut = ConfigBucket()
|
||||
|
||||
assert str(sut) == bucket_name
|
||||
|
||||
|
||||
class TestDataBucket:
|
||||
|
||||
def test_put_csv(self, s3_test, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- CSVファイルをPUTできること
|
||||
Arranges:
|
||||
- S3をモック化する
|
||||
- 環境変数をテスト用の値に置き換える
|
||||
Expects:
|
||||
- PUTしたファイルが存在する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.IMPORT_DATA_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.CRM_IMPORT_DATA_FOLDER', 'crm/target')
|
||||
|
||||
sut = DataBucket()
|
||||
sut.put_csv('test.csv', 'test,test,test')
|
||||
|
||||
actual = s3_client.get_object(Bucket=bucket_name, Key=f'crm/target/test.csv')
|
||||
assert actual['Body'].read().decode('utf-8') == 'test,test,test'
|
||||
|
||||
def test_raise_put_csv(self, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- CSVファイルをPUTできず、エラーになること
|
||||
Arranges:
|
||||
- s3_testフィクスチャを引数に指定しない
|
||||
- 環境変数を置き換える
|
||||
Expects:
|
||||
- CSVファイルをPUTできず、例外が発生する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.IMPORT_DATA_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.CRM_IMPORT_DATA_FOLDER', 'crm/target')
|
||||
|
||||
sut = DataBucket()
|
||||
with pytest.raises(Exception):
|
||||
sut.put_csv('test.csv', 'test,test,test')
|
||||
|
||||
def test_put_csv_from(self, s3_test, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- 他のバケットからCSVファイルをコピーできること
|
||||
Arranges:
|
||||
- S3をモック化する
|
||||
- コピー先バケットを作成する
|
||||
- 期待値となるファイルコピー元バケットに配置する
|
||||
- 環境変数をテスト用の値に置き換える
|
||||
Expects:
|
||||
- コピーしたファイルが存在する
|
||||
"""
|
||||
for_copy_bucket = 'for-copy-bucket'
|
||||
s3_client.create_bucket(Bucket=for_copy_bucket)
|
||||
s3_client.put_object(Bucket=bucket_name, Key='hogehoge/test.csv', Body=b'test,test,test')
|
||||
|
||||
monkeypatch.setattr('src.aws.s3.IMPORT_DATA_BUCKET', for_copy_bucket)
|
||||
monkeypatch.setattr('src.aws.s3.CRM_IMPORT_DATA_FOLDER', 'crm/target')
|
||||
|
||||
sut = DataBucket()
|
||||
sut.put_csv_from(bucket_name, 'hogehoge/test.csv')
|
||||
actual = s3_client.get_object(Bucket=for_copy_bucket, Key=f'crm/target/test.csv')
|
||||
|
||||
assert actual['Body'].read().decode('utf-8') == 'test,test,test'
|
||||
|
||||
def test_raise_put_csv_from(self, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- 他のバケットからCSVファイルをコピーできず、エラーになること
|
||||
Arranges:
|
||||
- s3_testフィクスチャを引数に指定しない
|
||||
- 環境変数を置き換える
|
||||
Expects:
|
||||
- コピーできず、例外が発生する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.IMPORT_DATA_BUCKET', 'for_copy_bucket')
|
||||
monkeypatch.setattr('src.aws.s3.CRM_IMPORT_DATA_FOLDER', 'crm/target')
|
||||
|
||||
sut = DataBucket()
|
||||
with pytest.raises(Exception):
|
||||
sut.put_csv_from(bucket_name, 'hogehoge/test.csv')
|
||||
|
||||
def test_data_bucket_str(self, s3_test, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- データ登録バケットを文字列化したとき、バケット名が取得できること
|
||||
Arranges:
|
||||
- 環境変数をテスト用の値に置き換える
|
||||
Expects:
|
||||
- 環境変数のデータ登録バケット名と一致する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.IMPORT_DATA_BUCKET', bucket_name)
|
||||
|
||||
sut = DataBucket()
|
||||
|
||||
assert str(sut) == bucket_name
|
||||
|
||||
|
||||
class TestBackupBucket:
|
||||
|
||||
def test_put_csv(self, s3_test, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- CSVファイルをPUTできること
|
||||
Arranges:
|
||||
- S3をモック化する
|
||||
- 環境変数をテスト用の値に置き換える
|
||||
Expects:
|
||||
- PUTしたファイルが存在する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.CRM_BACKUP_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.CRM_IMPORT_DATA_BACKUP_FOLDER', 'data_import')
|
||||
|
||||
sut = BackupBucket()
|
||||
sut.put_csv('test.csv', 'test,test,test')
|
||||
|
||||
actual = s3_client.get_object(Bucket=bucket_name, Key=f'data_import/test.csv')
|
||||
assert actual['Body'].read().decode('utf-8') == 'test,test,test'
|
||||
|
||||
def test_raise_put_csv_from(self, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- CSVファイルをPUTできず、エラーになること
|
||||
Arranges:
|
||||
- s3_testフィクスチャを引数に指定しない
|
||||
- 環境変数を置き換える
|
||||
Expects:
|
||||
- CSVファイルがPUTできず、例外が発生する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.IMPORT_DATA_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.CRM_IMPORT_DATA_FOLDER', 'crm/target')
|
||||
|
||||
sut = BackupBucket()
|
||||
with pytest.raises(Exception):
|
||||
sut.put_csv('test.csv', 'test,test,test')
|
||||
|
||||
def test_put_response_json(self, s3_test, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- JSONファイルをPUTできること
|
||||
Arranges:
|
||||
- S3をモック化する
|
||||
- 環境変数をテスト用の値に置き換える
|
||||
Expects:
|
||||
- PUTしたファイルが存在する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.CRM_BACKUP_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.RESPONSE_JSON_BACKUP_FOLDER', 'response_json')
|
||||
|
||||
sut = BackupBucket()
|
||||
sut.put_response_json('test.json', {"test": "test"})
|
||||
|
||||
actual = s3_client.get_object(Bucket=bucket_name, Key=f'response_json/test.json')
|
||||
assert actual['Body'].read().decode('utf-8') == '{"test": "test"}'
|
||||
|
||||
def test_raise_response_json(self, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- JSONファイルをPUTできず、エラーになること
|
||||
Arranges:
|
||||
- s3_testフィクスチャを引数に指定しない
|
||||
- 環境変数を置き換える
|
||||
Expects:
|
||||
- JSONファイルをPUTできず、例外が発生する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.CRM_BACKUP_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.RESPONSE_JSON_BACKUP_FOLDER', 'response_json')
|
||||
|
||||
sut = BackupBucket()
|
||||
with pytest.raises(Exception):
|
||||
sut.put_response_json('test.json', {"test": "test"})
|
||||
|
||||
def test_put_result_json(self, s3_test, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- 結果のJSONファイルをPUTできること
|
||||
Arranges:
|
||||
- S3をモック化する
|
||||
- 環境変数をテスト用の値に置き換える
|
||||
Expects:
|
||||
- PUTしたファイルが存在する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.CRM_BACKUP_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.PROCESS_RESULT_FOLDER', 'data_import')
|
||||
|
||||
sut = BackupBucket()
|
||||
sut.put_result_json('result.json', {"test": "test"})
|
||||
|
||||
actual = s3_client.get_object(Bucket=bucket_name, Key=f'data_import/result.json')
|
||||
assert actual['Body'].read().decode('utf-8') == '{"test": "test"}'
|
||||
|
||||
def test_raise_result_json(self, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- 結果のJSONファイルをPUTできず、エラーになること
|
||||
Arranges:
|
||||
- s3_testフィクスチャを引数に指定しない
|
||||
- 環境変数を置き換える
|
||||
Expects:
|
||||
- 結果のJSONファイルをPUTできず、例外が発生する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.CRM_BACKUP_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.PROCESS_RESULT_FOLDER', 'data_import')
|
||||
|
||||
sut = BackupBucket()
|
||||
with pytest.raises(Exception):
|
||||
sut.put_result_json('result.json', {"test": "test"})
|
||||
|
||||
def test_backup_bucket_str(self, s3_test, s3_client, bucket_name, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
- CRMデータバックアップバケットを文字列化したとき、バケット名が取得できること
|
||||
Arranges:
|
||||
- 環境変数をテスト用の値に置き換える
|
||||
Expects:
|
||||
- 環境変数のCRMデータバックアップバケット名と一致する
|
||||
"""
|
||||
monkeypatch.setattr('src.aws.s3.CRM_BACKUP_BUCKET', bucket_name)
|
||||
|
||||
sut = BackupBucket()
|
||||
|
||||
assert str(sut) == bucket_name
|
||||
@ -0,0 +1,467 @@
|
||||
import pytest
|
||||
from src.config.objects import FetchTargetObjects
|
||||
from src.parser.json_parser import JsonParser
|
||||
|
||||
|
||||
class TestFetchTargetObjects():
|
||||
|
||||
def test_constructor(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
インスタンス生成テスト
|
||||
辞書型のデータに対して、キーがあるかまた、キーの型が正しいかをチェック
|
||||
Arranges:
|
||||
- オブジェクト情報文字列を準備する
|
||||
- オブジェクト情報を辞書型にパースする
|
||||
Expects:
|
||||
- 例外が発生しないこと
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
fetch_objects = '''{
|
||||
"objects": [
|
||||
{
|
||||
"object_name": "AccountShare",
|
||||
"columns": [
|
||||
"Id",
|
||||
"AccountId",
|
||||
"UserOrGroupId",
|
||||
"AccountAccessLevel",
|
||||
"OpportunityAccessLevel",
|
||||
"CaseAccessLevel",
|
||||
"ContactAccessLevel",
|
||||
"RowCause",
|
||||
"LastModifiedDate",
|
||||
"LastModifiedById",
|
||||
"IsDeleted"
|
||||
],
|
||||
"is_skip": false,
|
||||
"is_update_last_fetch_datetime": true,
|
||||
"datetime_column": "LastModifiedDate"
|
||||
},
|
||||
{
|
||||
"object_name": "Contact",
|
||||
"columns": [
|
||||
"Id",
|
||||
"IsDeleted",
|
||||
"MasterRecordId",
|
||||
"AccountId",
|
||||
"IsPersonAccount",
|
||||
"LastName",
|
||||
"FirstName",
|
||||
"Salutation",
|
||||
"Name",
|
||||
"OtherStreet",
|
||||
"OtherCity",
|
||||
"OtherState",
|
||||
"OtherPostalCode",
|
||||
"OtherCountry",
|
||||
"OtherLatitude",
|
||||
"OtherLongitude",
|
||||
"OtherGeocodeAccuracy",
|
||||
"OtherAddress",
|
||||
"MailingStreet",
|
||||
"MailingCity",
|
||||
"MailingState",
|
||||
"MailingPostalCode",
|
||||
"MailingCountry",
|
||||
"MailingLatitude",
|
||||
"MailingLongitude",
|
||||
"MailingGeocodeAccuracy",
|
||||
"MailingAddress",
|
||||
"Phone",
|
||||
"Fax",
|
||||
"MobilePhone",
|
||||
"HomePhone",
|
||||
"OtherPhone",
|
||||
"AssistantPhone",
|
||||
"ReportsToId",
|
||||
"Email",
|
||||
"Title",
|
||||
"Department",
|
||||
"AssistantName",
|
||||
"Birthdate",
|
||||
"Description",
|
||||
"OwnerId",
|
||||
"HasOptedOutOfEmail",
|
||||
"HasOptedOutOfFax",
|
||||
"DoNotCall",
|
||||
"CreatedDate",
|
||||
"CreatedById",
|
||||
"LastModifiedDate",
|
||||
"LastModifiedById",
|
||||
"SystemModstamp",
|
||||
"LastActivityDate",
|
||||
"LastCURequestDate",
|
||||
"LastCUUpdateDate",
|
||||
"MayEdit",
|
||||
"IsLocked",
|
||||
"LastViewedDate",
|
||||
"LastReferencedDate",
|
||||
"EmailBouncedReason",
|
||||
"EmailBouncedDate",
|
||||
"IsEmailBounced",
|
||||
"PhotoUrl",
|
||||
"Jigsaw",
|
||||
"JigsawContactId",
|
||||
"IndividualId",
|
||||
"Mobile_ID_vod__c",
|
||||
"H1Insights__H1_NPI_Value_for_Testing__c",
|
||||
"H1Insights__H1_Person_ID__c",
|
||||
"H1Insights__H1_Request_Status__c",
|
||||
"H1Insights__H1_URL__c",
|
||||
"H1Insights__NPI_Number__c",
|
||||
"H1Insights__NPI_Number_for_H1_Insights__c",
|
||||
"MSJ_Marketing_Cloud_Integration__c"
|
||||
],
|
||||
"is_skip": false,
|
||||
"is_update_last_fetch_datetime": true
|
||||
},
|
||||
{
|
||||
"object_name": "Territory2",
|
||||
"columns": [
|
||||
"Id",
|
||||
"Name",
|
||||
"Territory2TypeId",
|
||||
"Territory2ModelId",
|
||||
"ParentTerritory2Id",
|
||||
"Description",
|
||||
"ForecastUserId",
|
||||
"AccountAccessLevel",
|
||||
"OpportunityAccessLevel",
|
||||
"CaseAccessLevel",
|
||||
"ContactAccessLevel",
|
||||
"LastModifiedDate",
|
||||
"LastModifiedById",
|
||||
"SystemModstamp",
|
||||
"DeveloperName",
|
||||
"MSJ_Territory_Type__c",
|
||||
"MSJ_Level__c"
|
||||
],
|
||||
"is_skip": false,
|
||||
"is_update_last_fetch_datetime": true
|
||||
}
|
||||
]
|
||||
}'''
|
||||
|
||||
json_parser = JsonParser(fetch_objects)
|
||||
fetch_objects_dict = json_parser.parse()
|
||||
|
||||
# Act
|
||||
FetchTargetObjects(fetch_objects_dict)
|
||||
|
||||
# Expects
|
||||
pass
|
||||
|
||||
def test_raise_constructor_no_key(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
インスタンス生成テスト
|
||||
辞書型のデータに対して、必要なキーがない場合、例外が発生すること
|
||||
Arranges:
|
||||
- オブジェクト情報文字列を準備する
|
||||
Expects:
|
||||
- 例外が発生し期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
fetch_objects_dict = {
|
||||
"test_objects": [
|
||||
{
|
||||
"object_name": "AccountShare",
|
||||
"columns": [
|
||||
"Id",
|
||||
"AccountId",
|
||||
"UserOrGroupId",
|
||||
"AccountAccessLevel",
|
||||
"OpportunityAccessLevel",
|
||||
"CaseAccessLevel",
|
||||
"ContactAccessLevel",
|
||||
"RowCause",
|
||||
"LastModifiedDate",
|
||||
"LastModifiedById",
|
||||
"IsDeleted"
|
||||
],
|
||||
"is_skip": False,
|
||||
"is_update_last_fetch_datetime": False,
|
||||
"datetime_column": "LastModifiedDate"
|
||||
},
|
||||
{
|
||||
"object_name": "Contact",
|
||||
"columns": [
|
||||
"Id",
|
||||
"IsDeleted",
|
||||
"MasterRecordId",
|
||||
"AccountId",
|
||||
"IsPersonAccount",
|
||||
"LastName",
|
||||
"FirstName",
|
||||
"Salutation",
|
||||
"Name",
|
||||
"OtherStreet",
|
||||
"OtherCity",
|
||||
"OtherState",
|
||||
"OtherPostalCode",
|
||||
"OtherCountry",
|
||||
"OtherLatitude",
|
||||
"OtherLongitude",
|
||||
"OtherGeocodeAccuracy",
|
||||
"OtherAddress",
|
||||
"MailingStreet",
|
||||
"MailingCity",
|
||||
"MailingState",
|
||||
"MailingPostalCode",
|
||||
"MailingCountry",
|
||||
"MailingLatitude",
|
||||
"MailingLongitude",
|
||||
"MailingGeocodeAccuracy",
|
||||
"MailingAddress",
|
||||
"Phone",
|
||||
"Fax",
|
||||
"MobilePhone",
|
||||
"HomePhone",
|
||||
"OtherPhone",
|
||||
"AssistantPhone",
|
||||
"ReportsToId",
|
||||
"Email",
|
||||
"Title",
|
||||
"Department",
|
||||
"AssistantName",
|
||||
"Birthdate",
|
||||
"Description",
|
||||
"OwnerId",
|
||||
"HasOptedOutOfEmail",
|
||||
"HasOptedOutOfFax",
|
||||
"DoNotCall",
|
||||
"CreatedDate",
|
||||
"CreatedById",
|
||||
"LastModifiedDate",
|
||||
"LastModifiedById",
|
||||
"SystemModstamp",
|
||||
"LastActivityDate",
|
||||
"LastCURequestDate",
|
||||
"LastCUUpdateDate",
|
||||
"MayEdit",
|
||||
"IsLocked",
|
||||
"LastViewedDate",
|
||||
"LastReferencedDate",
|
||||
"EmailBouncedReason",
|
||||
"EmailBouncedDate",
|
||||
"IsEmailBounced",
|
||||
"PhotoUrl",
|
||||
"Jigsaw",
|
||||
"JigsawContactId",
|
||||
"IndividualId",
|
||||
"Mobile_ID_vod__c",
|
||||
"H1Insights__H1_NPI_Value_for_Testing__c",
|
||||
"H1Insights__H1_Person_ID__c",
|
||||
"H1Insights__H1_Request_Status__c",
|
||||
"H1Insights__H1_URL__c",
|
||||
"H1Insights__NPI_Number__c",
|
||||
"H1Insights__NPI_Number_for_H1_Insights__c",
|
||||
"MSJ_Marketing_Cloud_Integration__c"
|
||||
],
|
||||
"is_skip": False,
|
||||
"is_update_last_fetch_datetime": True
|
||||
},
|
||||
{
|
||||
"object_name": "Territory2",
|
||||
"columns": [
|
||||
"Id",
|
||||
"Name",
|
||||
"Territory2TypeId",
|
||||
"Territory2ModelId",
|
||||
"ParentTerritory2Id",
|
||||
"Description",
|
||||
"ForecastUserId",
|
||||
"AccountAccessLevel",
|
||||
"OpportunityAccessLevel",
|
||||
"CaseAccessLevel",
|
||||
"ContactAccessLevel",
|
||||
"LastModifiedDate",
|
||||
"LastModifiedById",
|
||||
"SystemModstamp",
|
||||
"DeveloperName",
|
||||
"MSJ_Territory_Type__c",
|
||||
"MSJ_Level__c"
|
||||
],
|
||||
"is_skip": False,
|
||||
"is_update_last_fetch_datetime": True
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
FetchTargetObjects(fetch_objects_dict)
|
||||
|
||||
# Expects
|
||||
assert str(e.value) == '「objects」キーは必須です'
|
||||
|
||||
def test_raise_constructor_no_type(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
インスタンス生成テスト
|
||||
辞書型のデータの対象のキーの値の型が想定と違う場合、例外が発生すること
|
||||
Arranges:
|
||||
- オブジェクト情報文字列を準備する
|
||||
Expects:
|
||||
- 例外が発生し期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
fetch_objects_dict = {
|
||||
"objects": "test_value"
|
||||
}
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
FetchTargetObjects(fetch_objects_dict)
|
||||
|
||||
# Expects
|
||||
assert str(e.value) == '「objects」キーの値は「<class \'list\'>」でなければなりません'
|
||||
|
||||
def test_constructor_iterator(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
インスタンス生成テスト
|
||||
登録されたオブジェクトリストをすべて取り出せること
|
||||
Arranges:
|
||||
- オブジェクト情報文字列を準備する
|
||||
Expects:
|
||||
- ループが最後まで回ること
|
||||
"""
|
||||
|
||||
fetch_objects_dict = {
|
||||
"objects": [
|
||||
{
|
||||
"object_name": "AccountShare",
|
||||
"columns": [
|
||||
"Id",
|
||||
"AccountId",
|
||||
"UserOrGroupId",
|
||||
"AccountAccessLevel",
|
||||
"OpportunityAccessLevel",
|
||||
"CaseAccessLevel",
|
||||
"ContactAccessLevel",
|
||||
"RowCause",
|
||||
"LastModifiedDate",
|
||||
"LastModifiedById",
|
||||
"IsDeleted"
|
||||
],
|
||||
"is_skip": False,
|
||||
"is_update_last_fetch_datetime": False,
|
||||
"datetime_column": "LastModifiedDate"
|
||||
},
|
||||
{
|
||||
"object_name": "Contact",
|
||||
"columns": [
|
||||
"Id",
|
||||
"IsDeleted",
|
||||
"MasterRecordId",
|
||||
"AccountId",
|
||||
"IsPersonAccount",
|
||||
"LastName",
|
||||
"FirstName",
|
||||
"Salutation",
|
||||
"Name",
|
||||
"OtherStreet",
|
||||
"OtherCity",
|
||||
"OtherState",
|
||||
"OtherPostalCode",
|
||||
"OtherCountry",
|
||||
"OtherLatitude",
|
||||
"OtherLongitude",
|
||||
"OtherGeocodeAccuracy",
|
||||
"OtherAddress",
|
||||
"MailingStreet",
|
||||
"MailingCity",
|
||||
"MailingState",
|
||||
"MailingPostalCode",
|
||||
"MailingCountry",
|
||||
"MailingLatitude",
|
||||
"MailingLongitude",
|
||||
"MailingGeocodeAccuracy",
|
||||
"MailingAddress",
|
||||
"Phone",
|
||||
"Fax",
|
||||
"MobilePhone",
|
||||
"HomePhone",
|
||||
"OtherPhone",
|
||||
"AssistantPhone",
|
||||
"ReportsToId",
|
||||
"Email",
|
||||
"Title",
|
||||
"Department",
|
||||
"AssistantName",
|
||||
"Birthdate",
|
||||
"Description",
|
||||
"OwnerId",
|
||||
"HasOptedOutOfEmail",
|
||||
"HasOptedOutOfFax",
|
||||
"DoNotCall",
|
||||
"CreatedDate",
|
||||
"CreatedById",
|
||||
"LastModifiedDate",
|
||||
"LastModifiedById",
|
||||
"SystemModstamp",
|
||||
"LastActivityDate",
|
||||
"LastCURequestDate",
|
||||
"LastCUUpdateDate",
|
||||
"MayEdit",
|
||||
"IsLocked",
|
||||
"LastViewedDate",
|
||||
"LastReferencedDate",
|
||||
"EmailBouncedReason",
|
||||
"EmailBouncedDate",
|
||||
"IsEmailBounced",
|
||||
"PhotoUrl",
|
||||
"Jigsaw",
|
||||
"JigsawContactId",
|
||||
"IndividualId",
|
||||
"Mobile_ID_vod__c",
|
||||
"H1Insights__H1_NPI_Value_for_Testing__c",
|
||||
"H1Insights__H1_Person_ID__c",
|
||||
"H1Insights__H1_Request_Status__c",
|
||||
"H1Insights__H1_URL__c",
|
||||
"H1Insights__NPI_Number__c",
|
||||
"H1Insights__NPI_Number_for_H1_Insights__c",
|
||||
"MSJ_Marketing_Cloud_Integration__c"
|
||||
],
|
||||
"is_skip": False,
|
||||
"is_update_last_fetch_datetime": True
|
||||
},
|
||||
{
|
||||
"object_name": "Territory2",
|
||||
"columns": [
|
||||
"Id",
|
||||
"Name",
|
||||
"Territory2TypeId",
|
||||
"Territory2ModelId",
|
||||
"ParentTerritory2Id",
|
||||
"Description",
|
||||
"ForecastUserId",
|
||||
"AccountAccessLevel",
|
||||
"OpportunityAccessLevel",
|
||||
"CaseAccessLevel",
|
||||
"ContactAccessLevel",
|
||||
"LastModifiedDate",
|
||||
"LastModifiedById",
|
||||
"SystemModstamp",
|
||||
"DeveloperName",
|
||||
"MSJ_Territory_Type__c",
|
||||
"MSJ_Level__c"
|
||||
],
|
||||
"is_skip": False,
|
||||
"is_update_last_fetch_datetime": True
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Act
|
||||
sut = FetchTargetObjects(fetch_objects_dict)
|
||||
for i, item in enumerate(sut, 1):
|
||||
assert item is not None
|
||||
|
||||
# Expects
|
||||
assert i == 3
|
||||
@ -0,0 +1,380 @@
|
||||
import pytest
|
||||
from src.config.objects import LastFetchDatetime
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
|
||||
class TestLastFetchDatetime():
|
||||
def test_constructor(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
インスタンス生成テスト
|
||||
辞書型のデータに対して、バリデーションチェックを行いチェックが通ることを確認する
|
||||
Arranges:
|
||||
- 辞書型の前回取得日時データを準備する
|
||||
- 実行日時インスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生しないこと
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": "2022-01-01T00:00:00.000Z"
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
# Act
|
||||
LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Expects
|
||||
pass
|
||||
|
||||
def test_raise_constructor_last_fetch_datetime_from_no_key(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
インスタンス生成テスト
|
||||
辞書型のデータに対して、必要なキー(last_fetch_datetime_from)がない場合、例外が発生すること
|
||||
Arranges:
|
||||
- 辞書型の前回取得日時データを準備する
|
||||
- 実行日時インスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生し期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_to": "2022-01-01T00:00:00.000Z"
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Expects
|
||||
assert str(e.value) == '「last_fetch_datetime_from」キーは必須です'
|
||||
|
||||
def test_raise_constructor_last_fetch_datetime_from_no_value(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
インスタンス生成テスト
|
||||
辞書型のデータの対象のキー(last_fetch_datetime_from)の値が空文字の場合、例外が発生すること
|
||||
Arranges:
|
||||
- 辞書型の前回取得日時データを準備する
|
||||
- 実行日時インスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生し期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": "",
|
||||
"last_fetch_datetime_to": "2022-01-01T00:00:00.000Z"
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Expects
|
||||
assert str(e.value) == '「last_fetch_datetime_from」キーは必須です'
|
||||
|
||||
def test_raise_constructor_last_fetch_datetime_from_none_value(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
インスタンス生成テスト
|
||||
辞書型のデータの対象のキー(last_fetch_datetime_from)の値がNoneの場合、例外が発生すること
|
||||
Arranges:
|
||||
- 辞書型の前回取得日時データを準備する
|
||||
- 実行日時インスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生し期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": None,
|
||||
"last_fetch_datetime_to": "2022-01-01T00:00:00.000Z"
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Expects
|
||||
assert str(e.value) == '「last_fetch_datetime_from」キーは必須です'
|
||||
|
||||
def test_raise_constructor_last_fetch_datetime_from_other_type(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
インスタンス生成テスト
|
||||
辞書型のデータの対象のキー(last_fetch_datetime_from)の値の型が想定と違う場合、例外が発生すること
|
||||
Arranges:
|
||||
- 辞書型の前回取得日時データを準備する
|
||||
- 実行日時インスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生し期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": 1,
|
||||
"last_fetch_datetime_to": "2022-01-01T00:00:00.000Z"
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Expects
|
||||
assert str(e.value) == '「last_fetch_datetime_from」キーの値は「<class \'str\'>」でなければなりません'
|
||||
|
||||
def test_raise_constructor_last_fetch_datetime_from_other_string(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
インスタンス生成テスト
|
||||
辞書型のデータの対象のキー(last_fetch_datetime_from)の値が正規表現と違う場合、例外が発生すること
|
||||
Arranges:
|
||||
- 辞書型のオブジェクト情報を準備する
|
||||
- 実行日時インスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生し期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": "aaa",
|
||||
"last_fetch_datetime_to": "2022-01-01T00:00:00.000Z"
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Expects
|
||||
assert str(
|
||||
e.value) == '「last_fetch_datetime_from」キーの値の正規表現チェックに失敗しました 「YYYY-MM-DDTHH:MM:SS.000Z」形式である必要があります'
|
||||
|
||||
def test_raise_constructor_last_fetch_datetime_to_no_key(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
インスタンス生成テスト
|
||||
辞書型のデータに対して、キー(last_fetch_datetime_to)がない場合、例外が発生しないこと
|
||||
Arranges:
|
||||
- 辞書型の前回取得日時データを準備する
|
||||
- 実行日時インスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生しないこと
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z"
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
# Act
|
||||
LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Expects
|
||||
pass
|
||||
|
||||
def test_constructor_last_fetch_datetime_to_no_value(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
辞書型のデータの対象のキー(last_fetch_datetime_to)の値の型が空文字の場合、例外が発生しないこと
|
||||
Arranges:
|
||||
- 辞書型の前回取得日時データを準備する
|
||||
- 実行日時インスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生しないこと
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
# Act
|
||||
LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Expects
|
||||
None
|
||||
|
||||
def test_constructor_last_fetch_datetime_to_none__value(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
辞書型のデータの対象のキー(last_fetch_datetime_to)の値の型がNoneの場合、例外が発生しないこと
|
||||
Arranges:
|
||||
- 辞書型の前回取得日時データを準備する
|
||||
- 実行日時インスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生しないこと
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": None
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
# Act
|
||||
LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Expects
|
||||
pass
|
||||
|
||||
def test_raise_constructor_last_fetch_datetime_to_other_type(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
インスタンス生成テスト
|
||||
辞書型のデータの対象のキー(last_fetch_datetime_to)の値の型が想定と違う場合、例外が発生すること
|
||||
Arranges:
|
||||
- 辞書型の前回取得日時データを準備する
|
||||
- 実行日時インスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生し期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": 1
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Expects
|
||||
assert str(e.value) == '「last_fetch_datetime_to」キーの値は「<class \'str\'>」でなければなりません'
|
||||
|
||||
def test_raise_constructor_last_fetch_datetime_to_other_string(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
インスタンス生成テスト
|
||||
辞書型のデータの対象のキー(last_fetch_datetime_to)の値が正規表現と違う場合、例外が発生すること
|
||||
Arranges:
|
||||
- 辞書型の前回取得日時データを準備する
|
||||
- 実行日時インスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生し期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": "aaa"
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Expects
|
||||
assert str(
|
||||
e.value) == '「last_fetch_datetime_to」キーの値の正規表現チェックに失敗しました 「YYYY-MM-DDTHH:MM:SS.000Z」形式である必要があります'
|
||||
|
||||
def test_last_fetch_datetime_from(self) -> str:
|
||||
"""
|
||||
Cases:
|
||||
オブジェクト情報から対象の値を返すこと
|
||||
Arranges:
|
||||
- 辞書型の前回取得日時データを準備する
|
||||
- 実行日時インスタンスを生成する
|
||||
- 前回取得日時インスタンスを生成する
|
||||
Expects:
|
||||
- 戻り値が期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": "2022-01-01T00:00:00.000Z"
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
sut = LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Act
|
||||
actual = sut.last_fetch_datetime_from
|
||||
|
||||
# Expects
|
||||
assert actual == '1900-01-01T00:00:00.000Z'
|
||||
|
||||
def test_last_fetch_datetime_to(self) -> str:
|
||||
"""
|
||||
Cases:
|
||||
オブジェクト情報から対象の値を返すこと
|
||||
Arranges:
|
||||
- 辞書型の前回取得日時データを準備する
|
||||
- 実行日時インスタンスを生成する
|
||||
- 前回取得日時インスタンスを生成する
|
||||
Expects:
|
||||
- 戻り値が期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": "2022-01-01T00:00:00.000Z"
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
sut = LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Act
|
||||
actual = sut.last_fetch_datetime_to
|
||||
|
||||
# Expects
|
||||
assert actual == '2022-01-01T00:00:00.000Z'
|
||||
|
||||
def test_last_fetch_datetime_to_default(self) -> str:
|
||||
"""
|
||||
Cases:
|
||||
オブジェクト情報から対象の値を返すこと
|
||||
Arranges:
|
||||
- 辞書型の前回取得日時データを準備する
|
||||
- 実行日時インスタンスを生成する
|
||||
- 前回取得日時インスタンスを生成する
|
||||
Expects:
|
||||
- 戻り値が期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
sut = LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Act
|
||||
actual = sut.last_fetch_datetime_to
|
||||
|
||||
# Expects
|
||||
assert actual == str(execute_datetime)
|
||||
1802
ecs/crm-datafetch/tests/config/test_objects_target_object.py
Normal file
1802
ecs/crm-datafetch/tests/config/test_objects_target_object.py
Normal file
File diff suppressed because it is too large
Load Diff
79
ecs/crm-datafetch/tests/conftest.py
Normal file
79
ecs/crm-datafetch/tests/conftest.py
Normal file
@ -0,0 +1,79 @@
|
||||
"""pytestでフィクスチャやフック(テスト実行前後に差し込む処理)を管理するモジュール"""
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
import boto3
|
||||
import pytest
|
||||
from moto import mock_s3
|
||||
from py.xml import html # type: ignore
|
||||
|
||||
from . import docstring_parser
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def aws_credentials():
|
||||
"""Mocked AWS Credentials for moto."""
|
||||
os.environ["AWS_ACCESS_KEY_ID"] = "testing"
|
||||
os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
|
||||
os.environ["AWS_SECURITY_TOKEN"] = "testing"
|
||||
os.environ["AWS_SESSION_TOKEN"] = "testing"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def s3_client(aws_credentials):
|
||||
with mock_s3():
|
||||
conn = boto3.client("s3", region_name="us-east-1")
|
||||
yield conn
|
||||
|
||||
|
||||
# 以下、レポート出力用の設定
|
||||
|
||||
def pytest_html_report_title(report):
|
||||
# レポートタイトル
|
||||
report.title = "CRMデータ連携 CRMデータ取得機能 単体機能テスト結果報告書"
|
||||
|
||||
|
||||
def pytest_html_results_table_header(cells):
|
||||
del cells[2:]
|
||||
cells.insert(3, html.th("Cases"))
|
||||
cells.insert(4, html.th("Arranges"))
|
||||
cells.insert(5, html.th("Expects"))
|
||||
cells.append(html.th("Time", class_="sortable time", col="time"))
|
||||
|
||||
|
||||
def pytest_html_results_table_row(report, cells):
|
||||
del cells[2:]
|
||||
cells.insert(3, html.td(html.pre(report.cases))) # 「テスト内容」をレポートに出力
|
||||
cells.insert(4, html.td(html.pre(report.arranges))) # 「期待結果」をレポートに出力
|
||||
cells.insert(5, html.td(html.pre(report.expects))) # 「期待結果」をレポートに出力
|
||||
cells.append(html.td(datetime.now(), class_="col-time")) # ついでに「時間」もレポートに出力
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_makereport(item, call):
|
||||
outcome = yield
|
||||
report = outcome.get_result()
|
||||
docstring = docstring_parser.parse(str(item.function.__doc__))
|
||||
report.cases = docstring.get("Cases", '') # 「テスト内容」を`report`に追加
|
||||
report.arranges = docstring.get("Arranges", '') # 「準備作業」を`report`に追加
|
||||
report.expects = docstring.get("Expects", '') # 「期待結果」を`report`に追加
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
"--walk-through", action="store_true", default=False, help="run walk through tests"
|
||||
)
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
config.addinivalue_line("markers", "slow: mark test as slow to run")
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(config, items):
|
||||
skip_slow = pytest.mark.skip(reason="need --walk-through option to run")
|
||||
if config.getoption("--walk-through"):
|
||||
[item.add_marker(skip_slow) for item in items if "walk_through" not in item.keywords]
|
||||
return
|
||||
for item in items:
|
||||
if "walk_through" in item.keywords:
|
||||
item.add_marker(skip_slow)
|
||||
357
ecs/crm-datafetch/tests/converter/test_convert_strategy.py
Normal file
357
ecs/crm-datafetch/tests/converter/test_convert_strategy.py
Normal file
@ -0,0 +1,357 @@
|
||||
from collections import OrderedDict
|
||||
|
||||
from src.converter.convert_strategy import (BooleanConvertStrategy,
|
||||
ConvertStrategyFactory,
|
||||
DatetimeConvertStrategy,
|
||||
DictConvertStrategy,
|
||||
IntConvertStrategy,
|
||||
NoneValueConvertStrategy,
|
||||
StringConvertStrategy)
|
||||
|
||||
|
||||
class TestConvertStrategyFactory:
|
||||
|
||||
def test_create_none(self):
|
||||
"""
|
||||
Cases:
|
||||
引数にnull(None)を指定した場合、NoneValueConvertStrategyインスタンスが返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = ConvertStrategyFactory()
|
||||
actual = sut.create(None)
|
||||
|
||||
# Expects
|
||||
assert type(actual) == NoneValueConvertStrategy
|
||||
|
||||
def test_create_float(self):
|
||||
"""
|
||||
Cases:
|
||||
引数に指数表記を指定した場合、StringConvertStrategyインスタンスが返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = ConvertStrategyFactory()
|
||||
actual = sut.create(1.2345678E7)
|
||||
|
||||
# Expects
|
||||
# 変換しない
|
||||
assert type(actual) == StringConvertStrategy
|
||||
|
||||
def test_create_float_scale(self):
|
||||
"""
|
||||
Cases:
|
||||
引数に少数を指定した場合、StringConvertStrategyインスタンスが返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = ConvertStrategyFactory()
|
||||
actual = sut.create(1.2345678)
|
||||
|
||||
# Expects
|
||||
# 変換しない
|
||||
assert type(actual) == StringConvertStrategy
|
||||
|
||||
def test_create_bool_true(self):
|
||||
"""
|
||||
Cases:
|
||||
引数に論理値(True)を指定した場合、BooleanConvertStrategyインスタンスが返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = ConvertStrategyFactory()
|
||||
actual = sut.create(True)
|
||||
|
||||
# Expects
|
||||
assert type(actual) == BooleanConvertStrategy
|
||||
|
||||
def test_create_bool_false(self):
|
||||
"""
|
||||
Cases:
|
||||
引数に論理値(False)を指定した場合、BooleanConvertStrategyインスタンスが返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = ConvertStrategyFactory()
|
||||
actual = sut.create(False)
|
||||
|
||||
# Expects
|
||||
assert type(actual) == BooleanConvertStrategy
|
||||
|
||||
def test_create_datetime(self):
|
||||
"""
|
||||
Cases:
|
||||
引数に文字列かつ日付文字列を指定した場合、DatetimeConvertStrategyインスタンスが返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = ConvertStrategyFactory()
|
||||
actual = sut.create('2022-06-13T10:15:32.000+0000')
|
||||
|
||||
# Expects
|
||||
assert type(actual) == DatetimeConvertStrategy
|
||||
|
||||
def test_create_str(self):
|
||||
"""
|
||||
Cases:
|
||||
引数にSalesforce日付型以外の文字列を指定した場合、StringConvertStrategyインスタンスが返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = ConvertStrategyFactory()
|
||||
actual = sut.create('test_string')
|
||||
|
||||
# Expects
|
||||
assert type(actual) == StringConvertStrategy
|
||||
|
||||
def test_create_int(self):
|
||||
"""
|
||||
Cases:
|
||||
引数に整数を指定した場合、IntConvertStrategyインスタンスが返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = ConvertStrategyFactory()
|
||||
actual = sut.create(100)
|
||||
|
||||
# Expects
|
||||
assert type(actual) == IntConvertStrategy
|
||||
|
||||
def test_create_dict(self):
|
||||
"""
|
||||
Cases:
|
||||
引数に辞書型の値を指定した場合、IntConvertStrategyインスタンスが返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = ConvertStrategyFactory()
|
||||
actual = sut.create({'key': 'value'})
|
||||
|
||||
# Expects
|
||||
assert type(actual) == DictConvertStrategy
|
||||
|
||||
def test_create_ordered_dict_dict(self):
|
||||
"""
|
||||
Cases:
|
||||
引数に辞書型の値を指定した場合、IntConvertStrategyインスタンスが返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = ConvertStrategyFactory()
|
||||
actual = sut.create(OrderedDict([('key', 'value')]))
|
||||
|
||||
# Expects
|
||||
assert type(actual) == DictConvertStrategy
|
||||
|
||||
|
||||
class TestNoneValueConvertStrategy:
|
||||
|
||||
def test_convert_value(self) -> str:
|
||||
"""
|
||||
Cases:
|
||||
引数にnull(None)を指定した場合、''(空文字)が返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = NoneValueConvertStrategy()
|
||||
actual = sut.convert_value(None)
|
||||
|
||||
# Expects
|
||||
assert actual == ""
|
||||
|
||||
|
||||
class TestBooleanConvertStrategy:
|
||||
|
||||
def test_convert_value_true(self) -> bool:
|
||||
"""
|
||||
Cases:
|
||||
引数に論理値(True)を指定した場合、数値(1)が返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = BooleanConvertStrategy()
|
||||
actual = sut.convert_value(True)
|
||||
|
||||
# Expects
|
||||
assert actual == 1
|
||||
|
||||
def test_convert_value_false(self) -> bool:
|
||||
"""
|
||||
Cases:
|
||||
引数に論理値(False)を指定した場合、数値(0)が返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = BooleanConvertStrategy()
|
||||
actual = sut.convert_value(False)
|
||||
|
||||
# Expects
|
||||
assert actual == 0
|
||||
|
||||
|
||||
class TestDatetimeConvertStrategy:
|
||||
|
||||
def test_convert_value(self) -> str:
|
||||
"""
|
||||
Cases:
|
||||
引数に日付文字列を指定した場合、YYYY-MM-DD HH:MM:SSがJSTで返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = DatetimeConvertStrategy()
|
||||
actual = sut.convert_value('2022-06-13T20:15:32.000+0000')
|
||||
|
||||
# Expects
|
||||
assert actual == "2022-06-14 05:15:32"
|
||||
|
||||
|
||||
class TestIntConvertStrategy:
|
||||
|
||||
def test_convert_value(self):
|
||||
"""
|
||||
Cases:
|
||||
引数に整数を指定した場合、加工されず整数が返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = IntConvertStrategy()
|
||||
actual = sut.convert_value(100)
|
||||
|
||||
# Expects
|
||||
assert actual == 100
|
||||
|
||||
|
||||
class TestStringConvertStrategy:
|
||||
|
||||
def test_convert_value(self):
|
||||
"""
|
||||
Cases:
|
||||
引数に文字列を指定した場合、加工されず文字列が返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = StringConvertStrategy()
|
||||
actual = sut.convert_value('テストデータ')
|
||||
|
||||
# Expects
|
||||
assert actual == 'テストデータ'
|
||||
|
||||
|
||||
class TestDictConvertStrategy:
|
||||
|
||||
def test_convert_value_dict(self):
|
||||
"""
|
||||
Cases:
|
||||
引数に辞書型のデータを指定した場合、JSONの文字列が返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = DictConvertStrategy()
|
||||
actual = sut.convert_value({'テストデータキー': 'テストデータバリュー'})
|
||||
|
||||
# Expects
|
||||
assert actual == '{"テストデータキー": "テストデータバリュー"}'
|
||||
|
||||
def test_convert_value_dict_in_line_break(self):
|
||||
"""
|
||||
Cases:
|
||||
引数に辞書型のデータを指定した場合、JSONの文字列が返ってくること(バリューに改行を含む)
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = DictConvertStrategy()
|
||||
actual = sut.convert_value({'テストデータキー': 'テスト\nデータ\nバリュー'})
|
||||
|
||||
# Expects
|
||||
assert actual == '{"テストデータキー": "テスト\\nデータ\\nバリュー"}'
|
||||
|
||||
def test_convert_value_ordered_dict(self):
|
||||
"""
|
||||
Cases:
|
||||
引数に整列された辞書型のデータを指定した場合、JSONの文字列が返ってくること
|
||||
Arranges:
|
||||
- なし
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = DictConvertStrategy()
|
||||
actual = sut.convert_value(OrderedDict(
|
||||
[('テストデータキー', 'テストデータバリュー')]
|
||||
))
|
||||
|
||||
# Expects
|
||||
assert actual == '{"テストデータキー": "テストデータバリュー"}'
|
||||
393
ecs/crm-datafetch/tests/converter/test_converter.py
Normal file
393
ecs/crm-datafetch/tests/converter/test_converter.py
Normal file
@ -0,0 +1,393 @@
|
||||
import textwrap
|
||||
from collections import OrderedDict
|
||||
|
||||
import pytest
|
||||
from src.config.objects import TargetObject
|
||||
from src.converter.converter import CSVStringConverter
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
|
||||
class TestCSVStringConverter:
|
||||
|
||||
def test_convert(self) -> str:
|
||||
"""
|
||||
Cases:
|
||||
入力データがCSV形式の文字列で出力されること
|
||||
Arranges:
|
||||
- オブジェクト情報の作成
|
||||
- データの作成
|
||||
- 実行日時取得インスタンスの生成
|
||||
- オブジェクト情報インスタンスの生成
|
||||
Expects:
|
||||
戻り値が期待値と一致すること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_info = {
|
||||
"object_name": "AccountShare",
|
||||
"columns": [
|
||||
"Id",
|
||||
"AccountId",
|
||||
"UserOrGroupId",
|
||||
"AccountAccessLevel",
|
||||
"OpportunityAccessLevel",
|
||||
"CaseAccessLevel",
|
||||
"ContactAccessLevel",
|
||||
"RowCause",
|
||||
"LastModifiedDate",
|
||||
"LastModifiedById",
|
||||
"IsDeleted"
|
||||
],
|
||||
"is_skip": False,
|
||||
"is_update_last_fetch_datetime": False,
|
||||
"last_fetch_datetime_file_name": "AccountShare.json",
|
||||
"upload_file_name": "CRM_AccountShare_{execute_datetime}",
|
||||
"datetime_column": "LastModifiedDate"
|
||||
}
|
||||
|
||||
data = [
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'AccountShare'), ('url', '/services/data/v1.0/sobjects/AccountShare/test1')])),
|
||||
('Id', 'TEST001'),
|
||||
('AccountId', 'test001'),
|
||||
('UserOrGroupId', None),
|
||||
('AccountAccessLevel', 1),
|
||||
('OpportunityAccessLevel', 2),
|
||||
('CaseAccessLevel', 3),
|
||||
('ContactAccessLevel', 4),
|
||||
('RowCause', 'テストのため1'),
|
||||
('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
|
||||
('LastModifiedById', 1.234567E+6),
|
||||
('IsDeleted', False)
|
||||
]),
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'AccountShare'), ('url', '/services/data/v1.0/sobjects/AccountShare/test1')])),
|
||||
('Id', 'TEST002'),
|
||||
('AccountId', 'test002'),
|
||||
('UserOrGroupId', None),
|
||||
('AccountAccessLevel', 5),
|
||||
('OpportunityAccessLevel', 6),
|
||||
('CaseAccessLevel', 7),
|
||||
('ContactAccessLevel', 8),
|
||||
('RowCause', 'テストのため2'),
|
||||
('LastModifiedDate', '2022-06-02T16:30:30.000+0000'),
|
||||
('LastModifiedById', 2.23E+0),
|
||||
('IsDeleted', True)
|
||||
]),
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'AccountShare'), ('url', '/services/data/v1.0/sobjects/AccountShare/test1')])),
|
||||
('Id', 'TEST003'),
|
||||
('AccountId', 'test003'),
|
||||
('UserOrGroupId', None),
|
||||
('AccountAccessLevel', 9),
|
||||
('OpportunityAccessLevel', 10),
|
||||
('CaseAccessLevel', 11),
|
||||
('ContactAccessLevel', 12),
|
||||
('RowCause', 'テストのため3'),
|
||||
('LastModifiedDate', '2022-06-03T23:50:50.000+0000'),
|
||||
('LastModifiedById', 3.234567),
|
||||
('IsDeleted', False)
|
||||
])
|
||||
]
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(object_info, execute_datetime)
|
||||
|
||||
# Act
|
||||
csv_string_converter = CSVStringConverter(target_object, data)
|
||||
actual = csv_string_converter.convert()
|
||||
|
||||
# Expects
|
||||
expected_value = '''\
|
||||
"Id","AccountId","UserOrGroupId","AccountAccessLevel","OpportunityAccessLevel","CaseAccessLevel","ContactAccessLevel","RowCause","LastModifiedDate","LastModifiedById","IsDeleted"\r\n\
|
||||
"TEST001","test001","","1","2","3","4","テストのため1","2022-06-01 09:00:00","1234567.0","0"\r\n\
|
||||
"TEST002","test002","","5","6","7","8","テストのため2","2022-06-03 01:30:30","2.23","1"\r\n\
|
||||
"TEST003","test003","","9","10","11","12","テストのため3","2022-06-04 08:50:50","3.234567","0"\r\n\
|
||||
'''
|
||||
|
||||
# expected_valueのインデントが半角スペースと認識されてしまうため、`textwrap.dedent`にて補正
|
||||
assert actual == textwrap.dedent(expected_value)
|
||||
|
||||
def test_raise_convert_extract_jsons(self) -> str:
|
||||
"""
|
||||
Cases:
|
||||
一部データのキー(attributes)の不足により、JSONの成形部分で例外が発生すること
|
||||
Arranges:
|
||||
- オブジェクト情報の作成
|
||||
- データの作成
|
||||
- 実行日時取得インスタンスの生成
|
||||
- オブジェクト情報インスタンスの生成
|
||||
Expects:
|
||||
例外が期待値と一致すること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_info = {
|
||||
"object_name": "AccountShare",
|
||||
"columns": [
|
||||
"Id",
|
||||
"AccountId",
|
||||
"UserOrGroupId",
|
||||
"AccountAccessLevel",
|
||||
"OpportunityAccessLevel",
|
||||
"CaseAccessLevel",
|
||||
"ContactAccessLevel",
|
||||
"RowCause",
|
||||
"LastModifiedDate",
|
||||
"LastModifiedById",
|
||||
"IsDeleted"
|
||||
],
|
||||
"is_skip": False,
|
||||
"is_update_last_fetch_datetime": False,
|
||||
"last_fetch_datetime_file_name": "AccountShare.json",
|
||||
"upload_file_name": "CRM_AccountShare_{execute_datetime}",
|
||||
"datetime_column": "LastModifiedDate"
|
||||
}
|
||||
|
||||
data = [
|
||||
OrderedDict([
|
||||
('Id', 'TEST001'),
|
||||
('AccountId', 'test001'),
|
||||
('UserOrGroupId', None),
|
||||
('AccountAccessLevel', 1),
|
||||
('OpportunityAccessLevel', 2),
|
||||
('CaseAccessLevel', 3),
|
||||
('ContactAccessLevel', 4),
|
||||
('RowCause', 'テストのため1'),
|
||||
('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
|
||||
('LastModifiedById', 1.234567E+6),
|
||||
('IsDeleted', False)
|
||||
]),
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'AccountShare'), ('url', '/services/data/v1.0/sobjects/AccountShare/test1')])),
|
||||
('Id', 'TEST002'),
|
||||
('AccountId', 'test002'),
|
||||
('UserOrGroupId', None),
|
||||
('AccountAccessLevel', 5),
|
||||
('OpportunityAccessLevel', 6),
|
||||
('CaseAccessLevel', 7),
|
||||
('ContactAccessLevel', 8),
|
||||
('RowCause', 'テストのため2'),
|
||||
('LastModifiedDate', '2022-06-02T16:30:30.000+0000'),
|
||||
('LastModifiedById', 2.234567E+6),
|
||||
('IsDeleted', True)
|
||||
]),
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'AccountShare'), ('url', '/services/data/v1.0/sobjects/AccountShare/test1')])),
|
||||
('Id', 'TEST003'),
|
||||
('AccountId', 'test003'),
|
||||
('UserOrGroupId', None),
|
||||
('AccountAccessLevel', 9),
|
||||
('OpportunityAccessLevel', 10),
|
||||
('CaseAccessLevel', 11),
|
||||
('ContactAccessLevel', 12),
|
||||
('RowCause', 'テストのため3'),
|
||||
('LastModifiedDate', '2022-06-03T23:50:50.000+0000'),
|
||||
('LastModifiedById', 3.234567E+6),
|
||||
('IsDeleted', False)
|
||||
])
|
||||
]
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(object_info, execute_datetime)
|
||||
|
||||
# Act
|
||||
csv_string_converter = CSVStringConverter(target_object, data)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
csv_string_converter.convert()
|
||||
|
||||
# Expects
|
||||
assert '必要なjsonのデータ成形に失敗しました' in str(e.value)
|
||||
|
||||
def test_raise_convert_convert_to_csv(self) -> str:
|
||||
"""
|
||||
Cases:
|
||||
一部データのカラム(Id)の不足により、CSV変換で例外が発生すること
|
||||
Arranges:
|
||||
- オブジェクト情報の作成
|
||||
- データの作成
|
||||
- 実行日時取得インスタンスの生成
|
||||
- オブジェクト情報インスタンスの生成
|
||||
Expects:
|
||||
例外が期待値と一致すること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_info = {
|
||||
"object_name": "AccountShare",
|
||||
"columns": [
|
||||
"Id",
|
||||
"AccountId",
|
||||
"UserOrGroupId",
|
||||
"AccountAccessLevel",
|
||||
"OpportunityAccessLevel",
|
||||
"CaseAccessLevel",
|
||||
"ContactAccessLevel",
|
||||
"RowCause",
|
||||
"LastModifiedDate",
|
||||
"LastModifiedById",
|
||||
"IsDeleted"
|
||||
],
|
||||
"is_skip": False,
|
||||
"is_update_last_fetch_datetime": False,
|
||||
"last_fetch_datetime_file_name": "AccountShare.json",
|
||||
"upload_file_name": "CRM_AccountShare_{execute_datetime}",
|
||||
"datetime_column": "LastModifiedDate"
|
||||
}
|
||||
|
||||
data = [
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'AccountShare'), ('url', '/services/data/v1.0/sobjects/AccountShare/test1')])),
|
||||
('AccountId', 'test001'),
|
||||
('UserOrGroupId', None),
|
||||
('AccountAccessLevel', 1),
|
||||
('OpportunityAccessLevel', 2),
|
||||
('CaseAccessLevel', 3),
|
||||
('ContactAccessLevel', 4),
|
||||
('RowCause', 'テストのため1'),
|
||||
('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
|
||||
('LastModifiedById', 1.234567E+6),
|
||||
('IsDeleted', False)
|
||||
]),
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'AccountShare'), ('url', '/services/data/v1.0/sobjects/AccountShare/test1')])),
|
||||
('Id', 'TEST002'),
|
||||
('AccountId', 'test002'),
|
||||
('UserOrGroupId', None),
|
||||
('AccountAccessLevel', 5),
|
||||
('OpportunityAccessLevel', 6),
|
||||
('CaseAccessLevel', 7),
|
||||
('ContactAccessLevel', 8),
|
||||
('RowCause', 'テストのため2'),
|
||||
('LastModifiedDate', '2022-06-02T16:30:30.000+0000'),
|
||||
('LastModifiedById', 2.234567E+6),
|
||||
('IsDeleted', True)
|
||||
]),
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'AccountShare'), ('url', '/services/data/v1.0/sobjects/AccountShare/test1')])),
|
||||
('Id', 'TEST003'),
|
||||
('AccountId', 'test003'),
|
||||
('UserOrGroupId', None),
|
||||
('AccountAccessLevel', 9),
|
||||
('OpportunityAccessLevel', 10),
|
||||
('CaseAccessLevel', 11),
|
||||
('ContactAccessLevel', 12),
|
||||
('RowCause', 'テストのため3'),
|
||||
('LastModifiedDate', '2022-06-03T23:50:50.000+0000'),
|
||||
('LastModifiedById', 3.234567E+6),
|
||||
('IsDeleted', False)
|
||||
])
|
||||
]
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(object_info, execute_datetime)
|
||||
|
||||
# Act
|
||||
csv_string_converter = CSVStringConverter(target_object, data)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
csv_string_converter.convert()
|
||||
|
||||
# Expects
|
||||
assert 'CSV変換に失敗しました カラム名:[Id] 行番号: [1]' in str(e.value)
|
||||
|
||||
def test_raise_convert_write_csv_string(self, monkeypatch) -> str:
|
||||
"""
|
||||
Cases:
|
||||
csvデータ出力の、CSVデータ取得で例外が発生すること
|
||||
Arranges:
|
||||
- オブジェクト情報の作成
|
||||
- データの作成
|
||||
- 実行日時取得インスタンスの生成
|
||||
- オブジェクト情報インスタンスの生成
|
||||
- csvデータ出力のエラーを発生させるためのモックを準備
|
||||
Expects:
|
||||
例外が期待値と一致すること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_info = {
|
||||
"object_name": "AccountShare",
|
||||
"columns": [
|
||||
"Id",
|
||||
"AccountId",
|
||||
"UserOrGroupId",
|
||||
"AccountAccessLevel",
|
||||
"OpportunityAccessLevel",
|
||||
"CaseAccessLevel",
|
||||
"ContactAccessLevel",
|
||||
"RowCause",
|
||||
"LastModifiedDate",
|
||||
"LastModifiedById",
|
||||
"IsDeleted"
|
||||
],
|
||||
"is_skip": False,
|
||||
"is_update_last_fetch_datetime": False,
|
||||
"last_fetch_datetime_file_name": "AccountShare.json",
|
||||
"upload_file_name": "CRM_AccountShare_{execute_datetime}",
|
||||
"datetime_column": "LastModifiedDate"
|
||||
}
|
||||
|
||||
data = [
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'AccountShare'), ('url', '/services/data/v1.0/sobjects/AccountShare/test1')])),
|
||||
('Id', 'TEST001'),
|
||||
('AccountId', 'test001'),
|
||||
('UserOrGroupId', None),
|
||||
('AccountAccessLevel', 1),
|
||||
('OpportunityAccessLevel', 2),
|
||||
('CaseAccessLevel', 3),
|
||||
('ContactAccessLevel', 4),
|
||||
('RowCause', 'テストのため1'),
|
||||
('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
|
||||
('LastModifiedById', 1.234567E+6),
|
||||
('IsDeleted', False)
|
||||
]),
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'AccountShare'), ('url', '/services/data/v1.0/sobjects/AccountShare/test1')])),
|
||||
('Id', 'TEST002'),
|
||||
('AccountId', 'test002'),
|
||||
('UserOrGroupId', None),
|
||||
('AccountAccessLevel', 5),
|
||||
('OpportunityAccessLevel', 6),
|
||||
('CaseAccessLevel', 7),
|
||||
('ContactAccessLevel', 8),
|
||||
('RowCause', 'テストのため2'),
|
||||
('LastModifiedDate', '2022-06-02T16:30:30.000+0000'),
|
||||
('LastModifiedById', 2.234567E+6),
|
||||
('IsDeleted', True)
|
||||
]),
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'AccountShare'), ('url', '/services/data/v1.0/sobjects/AccountShare/test1')])),
|
||||
('Id', 'TEST003'),
|
||||
('AccountId', 'test003'),
|
||||
('UserOrGroupId', None),
|
||||
('AccountAccessLevel', 9),
|
||||
('OpportunityAccessLevel', 10),
|
||||
('CaseAccessLevel', 11),
|
||||
('ContactAccessLevel', 12),
|
||||
('RowCause', 'テストのため3'),
|
||||
('LastModifiedDate', '2022-06-03T23:50:50.000+0000'),
|
||||
('LastModifiedById', 3.234567E+6),
|
||||
('IsDeleted', False)
|
||||
])
|
||||
]
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(object_info, execute_datetime)
|
||||
|
||||
def dummy_method(arg):
|
||||
raise Exception(e)
|
||||
|
||||
# データ加工のみだと事前の処理によりエラーとなるため、csv出力モジュールをモック化する
|
||||
monkeypatch.setattr("csv.writer", dummy_method)
|
||||
|
||||
# Act
|
||||
csv_string_converter = CSVStringConverter(target_object, data)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
csv_string_converter.convert()
|
||||
|
||||
# Expects
|
||||
assert 'CSVデータの出力に失敗しました' in str(e.value)
|
||||
45
ecs/crm-datafetch/tests/docstring_parser.py
Normal file
45
ecs/crm-datafetch/tests/docstring_parser.py
Normal file
@ -0,0 +1,45 @@
|
||||
"""pytest-htmlでレポート出力するため、各テスト関数のドキュメントコメントのセクションを抜き出して、辞書にする
|
||||
Examples:
|
||||
<セクション名>:となっている部分が対象になる
|
||||
\"\"\"
|
||||
Cases:
|
||||
テストケース
|
||||
Arranges:
|
||||
準備作業
|
||||
Expects:
|
||||
期待値
|
||||
\"\"\"
|
||||
"""
|
||||
|
||||
import re
|
||||
from itertools import takewhile
|
||||
|
||||
_section_rgx = re.compile(r"^\s*[a-zA-Z]+:\s*$")
|
||||
_lspace_rgx = re.compile(r"^\s*")
|
||||
|
||||
|
||||
def _parse_section(lines: list) -> list:
|
||||
matches = map(lambda x: _section_rgx.match(x), lines)
|
||||
indexes = [i for i, x in enumerate(matches) if x is not None]
|
||||
return list(map(lambda x: (x, lines[x].strip()[: -1]), indexes))
|
||||
|
||||
|
||||
def _count_lspace(s: str) -> int:
|
||||
rgx = _lspace_rgx.match(s)
|
||||
if rgx is not None:
|
||||
return rgx.end()
|
||||
return 0
|
||||
|
||||
|
||||
def _parse_content(index: int, lines: list) -> str:
|
||||
lspace = _count_lspace(lines[index])
|
||||
i = index + 1
|
||||
contents = takewhile(lambda x: _count_lspace(x) > lspace, lines[i:])
|
||||
return "\n".join(map(lambda x: x.strip(), contents))
|
||||
|
||||
|
||||
def parse(docstring: str) -> dict:
|
||||
"""🚧sloppy docstring parser🚧"""
|
||||
lines = docstring.splitlines()
|
||||
sections = _parse_section(lines)
|
||||
return dict(map(lambda x: (x[1], _parse_content(x[0], lines)), sections))
|
||||
77
ecs/crm-datafetch/tests/parser/test_json_parser.py
Normal file
77
ecs/crm-datafetch/tests/parser/test_json_parser.py
Normal file
@ -0,0 +1,77 @@
|
||||
import pytest
|
||||
from src.parser.json_parser import JsonParser
|
||||
|
||||
|
||||
class TestJsonParser():
|
||||
|
||||
def test_parse(self) -> dict:
|
||||
"""
|
||||
Cases:
|
||||
- コメントアウトが記載されているJSONからコメントを取り除き、辞書型を返すこと
|
||||
Arranges:
|
||||
- JSON文字列を準備する
|
||||
Expects:
|
||||
- json.loadsされたファイルの内容が期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
json_string = """{
|
||||
"aaaa": "aaaa",
|
||||
# これはコメントです
|
||||
"#これはコメントではありません": "#これはコメントではありません",
|
||||
"bbb": false,
|
||||
"hogehoge": [
|
||||
"ccc",
|
||||
/これはコメントです
|
||||
"/これはコメントではありません"
|
||||
]
|
||||
}"""
|
||||
|
||||
# Act
|
||||
sut = JsonParser(json_string)
|
||||
actual = sut.parse()
|
||||
|
||||
# Expects
|
||||
expected_value = {
|
||||
"aaaa": "aaaa",
|
||||
"#これはコメントではありません": "#これはコメントではありません",
|
||||
"bbb": False,
|
||||
"hogehoge": [
|
||||
"ccc",
|
||||
"/これはコメントではありません"
|
||||
]
|
||||
}
|
||||
|
||||
assert actual == expected_value
|
||||
|
||||
def test_raise_parse(self) -> dict:
|
||||
"""
|
||||
Cases:
|
||||
- コメントアウト記号ではない文字をコメントアウトとしたときに、例外が発生すること
|
||||
Arranges:
|
||||
- JSON文字列を準備する
|
||||
Expects:
|
||||
- 例外が発生し期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
json_string = """{
|
||||
"aaaa": "aaaa",
|
||||
$ これはコメントです
|
||||
"#これはコメントではありません": "#これはコメントではありません",
|
||||
"bbb": false,
|
||||
"hogehoge": [
|
||||
"ccc",
|
||||
/これはコメントです
|
||||
"/これはコメントではありません"
|
||||
]
|
||||
}"""
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
|
||||
sut = JsonParser(json_string)
|
||||
sut.parse()
|
||||
|
||||
# Expects
|
||||
assert "Expecting property name enclosed in double quotes:" in str(e.value)
|
||||
0
ecs/crm-datafetch/tests/salesforce/__init__.py
Normal file
0
ecs/crm-datafetch/tests/salesforce/__init__.py
Normal file
662
ecs/crm-datafetch/tests/salesforce/test_salesforce.py
Normal file
662
ecs/crm-datafetch/tests/salesforce/test_salesforce.py
Normal file
@ -0,0 +1,662 @@
|
||||
"""
|
||||
!!!!注意!!!!
|
||||
当テストコードはSalesforceのレコードに依存しています。
|
||||
Accountオブジェクトの下記SFIDのレコードはいじらないようにしてください
|
||||
- 0015i00000LNywwAAD(テスト取引先1)
|
||||
- 0015i00000LOClSAAX(テスト取引先2)
|
||||
- 0015i00000LOCmGAAX(テスト取引先3)
|
||||
|
||||
変更してしまった場合は各SOQLの取得日付とデータを修正してください
|
||||
"""
|
||||
|
||||
from typing import OrderedDict
|
||||
|
||||
import pytest
|
||||
from requests.exceptions import ConnectTimeout, ReadTimeout
|
||||
from src.config.objects import LastFetchDatetime, TargetObject
|
||||
from src.salesforce.salesforce_api import SalesforceApiClient
|
||||
from src.salesforce.soql_builder import SOQLBuilder
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
|
||||
class TestSalesforceApiClient:
|
||||
|
||||
def test_fetch_sf_count(self):
|
||||
"""
|
||||
Cases:
|
||||
Salesforceからオブジェクトの件数が取得できること
|
||||
Arranges:
|
||||
SalesforceのAccountオブジェクトに、レコードを作成する(手作業、コード上では行わない)
|
||||
Expects:
|
||||
取得件数が1件以上になる
|
||||
"""
|
||||
soql = """SELECT
|
||||
COUNT(Id)
|
||||
FROM
|
||||
Account
|
||||
WHERE
|
||||
SystemModstamp > 2022-08-23T01:56:39.000Z AND
|
||||
SystemModstamp <= 2022-08-24T00:00:00.000Z
|
||||
"""
|
||||
sut = SalesforceApiClient()
|
||||
|
||||
actual = sut.fetch_sf_count(soql)
|
||||
assert actual >= 1
|
||||
|
||||
def test_fetch_sf_count_zero_record(self):
|
||||
"""
|
||||
Cases:
|
||||
取得範囲外の場合、Salesforceからオブジェクトの件数が取得できないこと
|
||||
Arranges:
|
||||
SalesforceのAccountオブジェクトに、レコードを作成する(手作業、コード上では行わない)
|
||||
Expects:
|
||||
取得件数が0件になる
|
||||
"""
|
||||
soql = """SELECT
|
||||
COUNT(Id)
|
||||
FROM
|
||||
Account
|
||||
WHERE
|
||||
SystemModstamp > 1999-01-01T00:00:00.000Z AND
|
||||
SystemModstamp <= 2000-01-01T00:00:00.000Z
|
||||
"""
|
||||
sut = SalesforceApiClient()
|
||||
|
||||
actual = sut.fetch_sf_count(soql)
|
||||
assert actual >= 0
|
||||
|
||||
def test_fetch_sf_count_by_soql_builder_system_modstamp_to_ge(self):
|
||||
"""
|
||||
Cases:
|
||||
- SOQLBuilderから生成したSOQLで、Salesforceからオブジェクトの件数が取得できること
|
||||
- SystemModStampがFrom指定日付未満のレコードは取得できないこと
|
||||
- SystemModStampのToが指定日付以上のレコードは取得できること
|
||||
Arranges:
|
||||
- SalesforceのAccountオブジェクトに、レコードを作成する
|
||||
- LastFetchDatetimeのFromがSystemModstamp未満になるように指定する(UTC指定)
|
||||
- LastFetchDatetimeのToがSystemModstampピッタリになるように指定する(UTC指定)
|
||||
Expects:
|
||||
取得件数が1になる
|
||||
"""
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
last_fetch_datetime = LastFetchDatetime({
|
||||
'last_fetch_datetime_from': '2022-08-23T02:38:59.000Z',
|
||||
'last_fetch_datetime_to': '2022-08-23T02:39:00.000Z',
|
||||
}, execute_datetime)
|
||||
target_object = TargetObject({
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name',
|
||||
'SystemModstamp',
|
||||
'LastModifiedDate',
|
||||
'CustomItem1__c',
|
||||
'CustomItem2__c',
|
||||
'CustomItem3__c',
|
||||
'CustomItem4__c',
|
||||
'CustomItem5__c',
|
||||
'CustomItem6__c',
|
||||
'CustomItem7__c',
|
||||
'CustomItem8__c'
|
||||
]
|
||||
}, execute_datetime)
|
||||
soql_builder = SOQLBuilder(target_object, last_fetch_datetime)
|
||||
soql = soql_builder.create_count_soql()
|
||||
sut = SalesforceApiClient()
|
||||
|
||||
actual = sut.fetch_sf_count(soql)
|
||||
assert actual == 1
|
||||
|
||||
def test_fetch_sf_count_by_soql_builder_system_modstamp_to_lt(self):
|
||||
"""
|
||||
Cases:
|
||||
- SOQLBuilderから生成したSOQLで、Salesforceからオブジェクトの件数が取得できること
|
||||
- SystemModStampのFromが指定日付のより大きいレコードは取得できること
|
||||
- SystemModStampのToが指定日付未満のレコードは取得できないこと
|
||||
Arranges:
|
||||
- SalesforceのAccountオブジェクトに、レコードを作成する
|
||||
- LastFetchDatetimeのFromがSystemModstampより大きくなるように指定する(UTC指定)
|
||||
- LastFetchDatetimeのToがSystemModstamp未満になるように指定する(UTC指定)
|
||||
Expects:
|
||||
取得件数が1になる
|
||||
"""
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
last_fetch_datetime = LastFetchDatetime({
|
||||
'last_fetch_datetime_from': '2022-08-23T02:38:00.000Z',
|
||||
'last_fetch_datetime_to': '2022-08-23T02:39:01.000Z',
|
||||
}, execute_datetime)
|
||||
target_object = TargetObject({
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name',
|
||||
'SystemModstamp',
|
||||
'LastModifiedDate',
|
||||
'CustomItem1__c',
|
||||
'CustomItem2__c',
|
||||
'CustomItem3__c',
|
||||
'CustomItem4__c',
|
||||
'CustomItem5__c',
|
||||
'CustomItem6__c',
|
||||
'CustomItem7__c',
|
||||
'CustomItem8__c'
|
||||
]
|
||||
}, execute_datetime)
|
||||
soql_builder = SOQLBuilder(target_object, last_fetch_datetime)
|
||||
soql = soql_builder.create_count_soql()
|
||||
sut = SalesforceApiClient()
|
||||
|
||||
actual = sut.fetch_sf_count(soql)
|
||||
assert actual == 1
|
||||
|
||||
def test_fetch_sf_count_by_soql_builder_system_modstamp_all_range(self):
|
||||
"""
|
||||
Cases:
|
||||
- SOQLBuilderから生成したSOQLで、Salesforceからオブジェクトの件数が取得できること
|
||||
- SystemModStampのFromが2000年1月1日以降のレコードが取得できること
|
||||
- SystemModStampのToが2100年12月31日未満のレコードが取得できること
|
||||
Arranges:
|
||||
- SalesforceのAccountオブジェクトに、レコードを作成する
|
||||
- LastFetchDatetimeのFromに2000年1月1日を指定する
|
||||
- LastFetchDatetimeのToに2100年12月31日を指定する
|
||||
Expects:
|
||||
取得件数が17になる
|
||||
"""
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
last_fetch_datetime = LastFetchDatetime({
|
||||
'last_fetch_datetime_from': '2000-01-01T00:00:00.000Z',
|
||||
'last_fetch_datetime_to': '2100-12-31T23:59:59.000Z',
|
||||
}, execute_datetime)
|
||||
target_object = TargetObject({
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name',
|
||||
'SystemModstamp',
|
||||
'LastModifiedDate',
|
||||
'CustomItem1__c',
|
||||
'CustomItem2__c',
|
||||
'CustomItem3__c',
|
||||
'CustomItem4__c',
|
||||
'CustomItem5__c',
|
||||
'CustomItem6__c',
|
||||
'CustomItem7__c',
|
||||
'CustomItem8__c'
|
||||
]
|
||||
}, execute_datetime)
|
||||
soql_builder = SOQLBuilder(target_object, last_fetch_datetime)
|
||||
soql = soql_builder.create_count_soql()
|
||||
sut = SalesforceApiClient()
|
||||
|
||||
actual = sut.fetch_sf_count(soql)
|
||||
assert actual == 17
|
||||
|
||||
def test_fetch_sf_data_one_record(self):
|
||||
"""
|
||||
Cases:
|
||||
Salesforceからオブジェクトが取得できること
|
||||
Arranges:
|
||||
SalesforceのAccountオブジェクトに、レコードを作成する(手作業、コード上では行わない)
|
||||
Expects:
|
||||
オブジェクトが取得でき、期待値と一致していること
|
||||
"""
|
||||
soql = """SELECT
|
||||
Id,
|
||||
Name,
|
||||
SystemModstamp,
|
||||
LastModifiedDate,
|
||||
CustomItem1__c,
|
||||
CustomItem2__c,
|
||||
CustomItem3__c,
|
||||
CustomItem4__c,
|
||||
CustomItem5__c,
|
||||
CustomItem6__c,
|
||||
CustomItem7__c,
|
||||
CustomItem8__c
|
||||
FROM
|
||||
Account
|
||||
WHERE
|
||||
SystemModstamp > 2022-08-23T01:56:39.000Z AND
|
||||
SystemModstamp <= 2022-08-24T00:00:00.000Z
|
||||
"""
|
||||
sut = SalesforceApiClient()
|
||||
|
||||
expect = {
|
||||
'Name': 'テスト取引先名1',
|
||||
'CustomItem1__c': 'テスト',
|
||||
'CustomItem2__c': 1.0,
|
||||
'CustomItem3__c': True,
|
||||
'CustomItem4__c': '01:15:00.000Z',
|
||||
'CustomItem5__c': '1;2;3;4',
|
||||
'CustomItem6__c': '改行ありの\r\nテスト\r\n項目です',
|
||||
'CustomItem7__c': '2022-08-04',
|
||||
'CustomItem8__c': '2022-08-04T03:00:00.000+0000',
|
||||
}
|
||||
actual = sut.fetch_sf_data(soql)
|
||||
assert len(actual) >= 1
|
||||
# 複数取れるが、アサーション対象は1つだけ
|
||||
actual = [record for record in actual if record['Name'] == 'テスト取引先名1']
|
||||
# Id, SystemModstamp, LastModifiedDateは自動生成なので、キーの有無だけ確認する
|
||||
# Attributesも
|
||||
assert 'Id' in actual[0].keys()
|
||||
assert 'SystemModstamp' in actual[0].keys()
|
||||
assert 'LastModifiedDate' in actual[0].keys()
|
||||
assert 'attributes' in actual[0].keys()
|
||||
|
||||
del actual[0]['Id']
|
||||
del actual[0]['SystemModstamp']
|
||||
del actual[0]['LastModifiedDate']
|
||||
del actual[0]['attributes']
|
||||
|
||||
assert dict(actual[0]) == expect
|
||||
|
||||
def test_fetch_sf_data_zero_record(self):
|
||||
"""
|
||||
Cases:
|
||||
取得範囲外の場合、Salesforceからオブジェクトが取得できないこと
|
||||
Arranges:
|
||||
SalesforceのAccountオブジェクトに、レコードを作成する(手作業、コード上では行わない)
|
||||
Expects:
|
||||
取得件数が0件になる
|
||||
"""
|
||||
soql = """SELECT
|
||||
Id,
|
||||
Name,
|
||||
SystemModstamp,
|
||||
LastModifiedDate,
|
||||
CustomItem1__c,
|
||||
CustomItem2__c,
|
||||
CustomItem3__c,
|
||||
CustomItem4__c,
|
||||
CustomItem5__c,
|
||||
CustomItem6__c,
|
||||
CustomItem7__c,
|
||||
CustomItem8__c
|
||||
FROM
|
||||
Account
|
||||
WHERE
|
||||
SystemModstamp > 1999-01-01T00:00:00.000Z AND
|
||||
SystemModstamp <= 2000-01-01T00:00:00.000Z
|
||||
"""
|
||||
sut = SalesforceApiClient()
|
||||
|
||||
actual = sut.fetch_sf_data(soql)
|
||||
assert len(actual) >= 0
|
||||
|
||||
def test_fetch_sf_data_by_soql_builder_system_modstamp_to_ge(self):
|
||||
"""
|
||||
Cases:
|
||||
- SOQLBuilderから生成したSOQLで、Salesforceからオブジェクトが取得できること
|
||||
- SystemModStampがFrom指定日付未満のレコードは取得できないこと
|
||||
- SystemModStampのToが指定日付以上のレコードは取得できること
|
||||
Arranges:
|
||||
- SalesforceのAccountオブジェクトに、レコードを作成する
|
||||
- LastFetchDatetimeのFromがSystemModstamp未満になるように指定する(UTC指定)
|
||||
- LastFetchDatetimeのToがSystemModstampピッタリになるように指定する(UTC指定)
|
||||
Expects:
|
||||
取得できたオブジェクト1件が期待値どおりであること
|
||||
"""
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
last_fetch_datetime = LastFetchDatetime({
|
||||
'last_fetch_datetime_from': '2022-08-23T03:48:39.000Z',
|
||||
'last_fetch_datetime_to': '2022-08-23T03:48:40.000Z',
|
||||
}, execute_datetime)
|
||||
target_object = TargetObject({
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name',
|
||||
'SystemModstamp',
|
||||
'LastModifiedDate',
|
||||
'CustomItem1__c',
|
||||
'CustomItem2__c',
|
||||
'CustomItem3__c',
|
||||
'CustomItem4__c',
|
||||
'CustomItem5__c',
|
||||
'CustomItem6__c',
|
||||
'CustomItem7__c',
|
||||
'CustomItem8__c'
|
||||
]
|
||||
}, execute_datetime)
|
||||
soql_builder = SOQLBuilder(target_object, last_fetch_datetime)
|
||||
soql = soql_builder.create_fetch_soql()
|
||||
sut = SalesforceApiClient()
|
||||
|
||||
actual = sut.fetch_sf_data(soql)
|
||||
assert len(actual) == 1
|
||||
|
||||
# Id, SystemModstamp, LastModifiedDateは自動生成なので、キーの有無だけ確認する
|
||||
# Attributesも
|
||||
assert 'Id' in actual[0].keys()
|
||||
assert 'SystemModstamp' in actual[0].keys()
|
||||
assert 'LastModifiedDate' in actual[0].keys()
|
||||
assert 'attributes' in actual[0].keys()
|
||||
|
||||
del actual[0]['Id']
|
||||
del actual[0]['SystemModstamp']
|
||||
del actual[0]['LastModifiedDate']
|
||||
del actual[0]['attributes']
|
||||
|
||||
expect = {
|
||||
'Name': 'テスト取引先名3',
|
||||
'CustomItem1__c': 'テスト3',
|
||||
'CustomItem2__c': 3.0,
|
||||
'CustomItem3__c': True,
|
||||
'CustomItem4__c': '00:45:00.000Z',
|
||||
'CustomItem5__c': '2;3',
|
||||
'CustomItem6__c': 'かいぎょ',
|
||||
'CustomItem7__c': '2022-08-06',
|
||||
'CustomItem8__c': '2022-08-06T00:00:00.000+0000',
|
||||
}
|
||||
|
||||
assert dict(actual[0]) == expect
|
||||
|
||||
def test_fetch_sf_data_by_soql_builder_system_modstamp_to_gt(self):
|
||||
"""
|
||||
Cases:
|
||||
- SOQLBuilderから生成したSOQLで、Salesforceからオブジェクトが取得できること
|
||||
- SystemModStampのFromが指定日付のより大きいレコードは取得できること
|
||||
- SystemModStampのToが指定日付未満のレコードは取得できないこと
|
||||
Arranges:
|
||||
- SalesforceのAccountオブジェクトに、レコードを作成する
|
||||
- LastFetchDatetimeのFromがSystemModstampより小さくなるように指定する(UTC指定)
|
||||
- LastFetchDatetimeのToがSystemModstampより大きくなるように指定する(UTC指定)
|
||||
Expects:
|
||||
取得できたオブジェクト1件が期待値どおりであること
|
||||
"""
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
last_fetch_datetime = LastFetchDatetime({
|
||||
'last_fetch_datetime_from': '2022-08-23T03:42:24.000Z',
|
||||
'last_fetch_datetime_to': '2022-08-23T03:42:26.000Z',
|
||||
}, execute_datetime)
|
||||
target_object = TargetObject({
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name',
|
||||
'SystemModstamp',
|
||||
'LastModifiedDate',
|
||||
'CustomItem1__c',
|
||||
'CustomItem2__c',
|
||||
'CustomItem3__c',
|
||||
'CustomItem4__c',
|
||||
'CustomItem5__c',
|
||||
'CustomItem6__c',
|
||||
'CustomItem7__c',
|
||||
'CustomItem8__c'
|
||||
]
|
||||
}, execute_datetime)
|
||||
soql_builder = SOQLBuilder(target_object, last_fetch_datetime)
|
||||
soql = soql_builder.create_fetch_soql()
|
||||
sut = SalesforceApiClient()
|
||||
|
||||
actual = sut.fetch_sf_data(soql)
|
||||
assert len(actual) == 1
|
||||
|
||||
# Id, SystemModstamp, LastModifiedDateは自動生成なので、キーの有無だけ確認する
|
||||
# Attributesも
|
||||
assert 'Id' in actual[0].keys()
|
||||
assert 'SystemModstamp' in actual[0].keys()
|
||||
assert 'LastModifiedDate' in actual[0].keys()
|
||||
assert 'attributes' in actual[0].keys()
|
||||
|
||||
del actual[0]['Id']
|
||||
del actual[0]['SystemModstamp']
|
||||
del actual[0]['LastModifiedDate']
|
||||
del actual[0]['attributes']
|
||||
|
||||
expect = {
|
||||
'Name': 'テスト取引先名2',
|
||||
'CustomItem1__c': 'テスト2',
|
||||
'CustomItem2__c': 2.0,
|
||||
'CustomItem3__c': False,
|
||||
'CustomItem4__c': '00:30:00.000Z',
|
||||
'CustomItem5__c': '1;4',
|
||||
'CustomItem6__c': '改行あり項目',
|
||||
'CustomItem7__c': '2022-08-05',
|
||||
'CustomItem8__c': '2022-08-04T23:30:00.000+0000',
|
||||
}
|
||||
|
||||
assert dict(actual[0]) == expect
|
||||
|
||||
def test_fetch_sf_data_by_soql_builder_address_item_check(self):
|
||||
"""
|
||||
Cases:
|
||||
- SOQLBuilderから生成したSOQLで、Salesforceからオブジェクトが取得できること
|
||||
- できること
|
||||
Arranges:
|
||||
- SalesforceのAccountオブジェクトに、住所項目を持つレコードを作成する
|
||||
- 住所項目を持つレコードだけが取れるよう日付を設定する
|
||||
Expects:
|
||||
取得できたオブジェクト件数が1件になる
|
||||
住所項目(BillingAddress)が想定通りの値になっていること
|
||||
"""
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
last_fetch_datetime = LastFetchDatetime({
|
||||
'last_fetch_datetime_from': '2022-08-23T02:38:00.000Z',
|
||||
'last_fetch_datetime_to': '2022-08-23T02:39:00.000Z',
|
||||
}, execute_datetime)
|
||||
target_object = TargetObject({
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name',
|
||||
'SystemModstamp',
|
||||
'LastModifiedDate',
|
||||
'BillingStreet',
|
||||
'BillingCity',
|
||||
'BillingState',
|
||||
'BillingPostalCode',
|
||||
'BillingCountry',
|
||||
'BillingLatitude',
|
||||
'BillingLongitude',
|
||||
'BillingGeocodeAccuracy',
|
||||
'BillingAddress',
|
||||
]
|
||||
}, execute_datetime)
|
||||
soql_builder = SOQLBuilder(target_object, last_fetch_datetime)
|
||||
soql = soql_builder.create_fetch_soql()
|
||||
sut = SalesforceApiClient()
|
||||
|
||||
actual = sut.fetch_sf_data(soql)
|
||||
assert len(actual) == 1
|
||||
expect_address = OrderedDict([
|
||||
("city", '〇〇区'),
|
||||
("country", "日本"),
|
||||
("geocodeAccuracy", None),
|
||||
("latitude", None),
|
||||
("longitude", None),
|
||||
("postalCode", '999-9999'),
|
||||
("state", '東京都'),
|
||||
("street", '△△-✗✗'),
|
||||
])
|
||||
|
||||
assert actual[0]['BillingAddress'] == expect_address
|
||||
assert actual[0]['BillingCity'] == '〇〇区'
|
||||
assert actual[0]['BillingCountry'] == '日本'
|
||||
assert actual[0]['BillingGeocodeAccuracy'] is None
|
||||
assert actual[0]['BillingLatitude'] is None
|
||||
assert actual[0]['BillingLongitude'] is None
|
||||
assert actual[0]['BillingPostalCode'] == '999-9999'
|
||||
assert actual[0]['BillingState'] == '東京都'
|
||||
assert actual[0]['BillingStreet'] == '△△-✗✗'
|
||||
|
||||
def test_fetch_sf_data_by_soql_builder_system_modstamp_all_range(self):
|
||||
"""
|
||||
Cases:
|
||||
- SOQLBuilderから生成したSOQLで、Salesforceからオブジェクトが取得できること
|
||||
- SystemModStampのFromが2000年1月1日以降のレコードが取得できること
|
||||
- SystemModStampのToが2100年12月31日未満のレコードが取得できること
|
||||
Arranges:
|
||||
- SalesforceのAccountオブジェクトに、レコードを作成する
|
||||
- LastFetchDatetimeのFromに2000年1月1日を指定する
|
||||
- LastFetchDatetimeのToに2100年12月31日を指定する
|
||||
Expects:
|
||||
取得できたオブジェクト件数が17件になる
|
||||
"""
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
last_fetch_datetime = LastFetchDatetime({
|
||||
'last_fetch_datetime_from': '2000-01-01T00:00:00.000Z',
|
||||
'last_fetch_datetime_to': '2100-12-31T23:59:59.000Z',
|
||||
}, execute_datetime)
|
||||
target_object = TargetObject({
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name',
|
||||
'SystemModstamp',
|
||||
'LastModifiedDate',
|
||||
'BillingAddress',
|
||||
'CustomItem1__c',
|
||||
'CustomItem2__c',
|
||||
'CustomItem3__c',
|
||||
'CustomItem4__c',
|
||||
'CustomItem5__c',
|
||||
'CustomItem6__c',
|
||||
'CustomItem7__c',
|
||||
'CustomItem8__c'
|
||||
]
|
||||
}, execute_datetime)
|
||||
soql_builder = SOQLBuilder(target_object, last_fetch_datetime)
|
||||
soql = soql_builder.create_fetch_soql()
|
||||
sut = SalesforceApiClient()
|
||||
|
||||
actual = sut.fetch_sf_data(soql)
|
||||
assert len(actual) == 17
|
||||
# 内容の確認は別のケースで行っているため省略
|
||||
|
||||
def test_raise_create_instance_cause_auth_failed(self, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
存在しないユーザを指定した場合、エラーが発生すること
|
||||
Arranges:
|
||||
CRMのユーザ名を保持する環境変数に、存在しないユーザー名を指定する
|
||||
Expects:
|
||||
ユーザ認証でエラーが発生すること
|
||||
"""
|
||||
monkeypatch.setattr('src.salesforce.salesforce_api.CRM_USER_NAME', 'invalid_username')
|
||||
with pytest.raises(Exception):
|
||||
SalesforceApiClient()
|
||||
|
||||
def test_raise_fetch_sf_count_auth_timeout(self, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
認証タイムアウトが発生した場合、エラーが発生すること
|
||||
Arranges:
|
||||
認証タイムアウト秒数を保持する環境変数に、0.0000000001を指定する
|
||||
Expects:
|
||||
コネクションタイムアウトエラーが発生すること
|
||||
"""
|
||||
monkeypatch.setattr('src.salesforce.salesforce_api.CRM_AUTH_TIMEOUT', 0.0000000001)
|
||||
sf = SalesforceApiClient()
|
||||
soql = """SELECT
|
||||
COUNT(Id)
|
||||
FROM
|
||||
Account
|
||||
WHERE
|
||||
SystemModstamp > 2022-08-04T00:00:00.000Z AND
|
||||
SystemModstamp <= 2022-08-06T00:00:00.000Z
|
||||
"""
|
||||
with pytest.raises(ConnectTimeout):
|
||||
sf.fetch_sf_count(soql)
|
||||
|
||||
def test_raise_fetch_sf_count_read_timeout(self, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
読み取りタイムアウトが発生した場合、エラーが発生すること
|
||||
Arranges:
|
||||
CRMの件数取得タイムアウト秒数を保持する環境変数に、0.0000000001を指定する
|
||||
Expects:
|
||||
読み取りタイムアウトエラーが発生すること
|
||||
"""
|
||||
monkeypatch.setattr('src.salesforce.salesforce_api.CRM_GET_RECORD_COUNT_TIMEOUT', 0.0000000001)
|
||||
sf = SalesforceApiClient()
|
||||
soql = """SELECT
|
||||
COUNT(Id)
|
||||
FROM
|
||||
Account
|
||||
WHERE
|
||||
SystemModstamp > 2022-08-04T00:00:00.000Z AND
|
||||
SystemModstamp <= 2022-08-06T00:00:00.000Z
|
||||
"""
|
||||
with pytest.raises(ReadTimeout):
|
||||
sf.fetch_sf_count(soql)
|
||||
|
||||
def test_raise_fetch_sf_count_invalid_soql(self, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
不正なSOQLを指定した場合に、エラーが発生すること
|
||||
Arranges:
|
||||
不正なSOQLを作成する
|
||||
Expects:
|
||||
エラーが発生すること
|
||||
"""
|
||||
sf = SalesforceApiClient()
|
||||
soql = "SELECT"
|
||||
with pytest.raises(Exception):
|
||||
sf.fetch_sf_count(soql)
|
||||
|
||||
def test_raise_fetch_sf_data_auth_timeout(self, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
認証タイムアウトが発生した場合、エラーが発生すること
|
||||
Arranges:
|
||||
認証タイムアウト秒数を保持する環境変数に、0.0000000001を指定する
|
||||
Expects:
|
||||
コネクションタイムアウトエラーが発生すること
|
||||
"""
|
||||
monkeypatch.setattr('src.salesforce.salesforce_api.CRM_AUTH_TIMEOUT', 0.0000000001)
|
||||
sf = SalesforceApiClient()
|
||||
soql = """SELECT
|
||||
COUNT(Id)
|
||||
FROM
|
||||
Account
|
||||
WHERE
|
||||
SystemModstamp > 2022-08-04T00:00:00.000Z AND
|
||||
SystemModstamp <= 2022-08-06T00:00:00.000Z
|
||||
"""
|
||||
with pytest.raises(ConnectTimeout):
|
||||
sf.fetch_sf_data(soql)
|
||||
|
||||
def test_raise_fetch_sf_data_read_timeout(self, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
読み取りタイムアウトが発生した場合、エラーが発生すること
|
||||
Arranges:
|
||||
CRMのデータ取得タイムアウト秒数を保持する環境変数に、0.0000000001を指定する
|
||||
Expects:
|
||||
読み取りタイムアウトエラーが発生すること
|
||||
"""
|
||||
monkeypatch.setattr('src.salesforce.salesforce_api.CRM_FETCH_RECORD_TIMEOUT', 0.0000000001)
|
||||
sf = SalesforceApiClient()
|
||||
soql = """SELECT
|
||||
COUNT(Id)
|
||||
FROM
|
||||
Account
|
||||
WHERE
|
||||
SystemModstamp > 2022-08-04T00:00:00.000Z AND
|
||||
SystemModstamp <= 2022-08-06T00:00:00.000Z
|
||||
"""
|
||||
with pytest.raises(ReadTimeout):
|
||||
sf.fetch_sf_data(soql)
|
||||
|
||||
def test_raise_fetch_sf_data_invalid_soql(self, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
不正なSOQLを指定した場合に、エラーが発生すること
|
||||
Arranges:
|
||||
不正なSOQLを作成する
|
||||
Expects:
|
||||
エラーが発生すること
|
||||
"""
|
||||
sf = SalesforceApiClient()
|
||||
soql = "SELECT"
|
||||
with pytest.raises(Exception):
|
||||
sf.fetch_sf_data(soql)
|
||||
162
ecs/crm-datafetch/tests/salesforce/test_soql_builder.py
Normal file
162
ecs/crm-datafetch/tests/salesforce/test_soql_builder.py
Normal file
@ -0,0 +1,162 @@
|
||||
|
||||
import pytest
|
||||
from src.config.objects import ExecuteDateTime, LastFetchDatetime, TargetObject
|
||||
from src.salesforce.soql_builder import SOQLBuilder
|
||||
|
||||
|
||||
class TestSOQLBuilder:
|
||||
|
||||
def test_create_count_soql(self):
|
||||
"""
|
||||
Cases:
|
||||
件数取得のSOQLが生成できること
|
||||
Arranges:
|
||||
SOQL生成用のパラメータを用意する
|
||||
Expects:
|
||||
期待値通りのSOQLが生成されること
|
||||
"""
|
||||
test_target_object_json = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name',
|
||||
'SystemModstamp',
|
||||
'LastModifiedDate',
|
||||
'CustomItem1__c',
|
||||
'CustomItem2__c',
|
||||
'CustomItem3__c',
|
||||
'CustomItem4__c',
|
||||
'CustomItem5__c',
|
||||
'CustomItem6__c',
|
||||
'CustomItem7__c',
|
||||
'CustomItem8__c'
|
||||
]
|
||||
}
|
||||
|
||||
test_last_fetch_datetime_json = {
|
||||
'last_fetch_datetime_from': '1999-01-01T00:00:00.000Z',
|
||||
'last_fetch_datetime_to': '2000-01-01T00:00:00.000Z',
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(test_target_object_json, execute_datetime)
|
||||
test_last_fetch_datetime = LastFetchDatetime(test_last_fetch_datetime_json, execute_datetime)
|
||||
|
||||
sut = SOQLBuilder(target_object, test_last_fetch_datetime)
|
||||
actual = sut.create_count_soql()
|
||||
|
||||
expect = """SELECT
|
||||
COUNT(Id)
|
||||
FROM
|
||||
Account
|
||||
WHERE
|
||||
SystemModstamp > 1999-01-01T00:00:00.000Z AND
|
||||
SystemModstamp <= 2000-01-01T00:00:00.000Z
|
||||
"""
|
||||
|
||||
assert actual.replace('\n', '').replace(' ', '') == expect.replace('\n', '').replace(' ', '')
|
||||
|
||||
def test_create_fetch_soql(self):
|
||||
"""
|
||||
Cases:
|
||||
データ取得用のSOQLが生成できること
|
||||
Arranges:
|
||||
SOQL生成用のパラメータを用意する
|
||||
Expects:
|
||||
期待値通りのSOQLが生成されること
|
||||
"""
|
||||
test_target_object_json = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name',
|
||||
'SystemModstamp',
|
||||
'LastModifiedDate',
|
||||
'CustomItem1__c',
|
||||
'CustomItem2__c',
|
||||
'CustomItem3__c',
|
||||
'CustomItem4__c',
|
||||
'CustomItem5__c',
|
||||
'CustomItem6__c',
|
||||
'CustomItem7__c',
|
||||
'CustomItem8__c'
|
||||
]
|
||||
}
|
||||
|
||||
test_last_fetch_datetime_json = {
|
||||
'last_fetch_datetime_from': '1999-01-01T00:00:00.000Z',
|
||||
'last_fetch_datetime_to': '2000-01-01T00:00:00.000Z',
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(test_target_object_json, execute_datetime)
|
||||
test_last_fetch_datetime = LastFetchDatetime(test_last_fetch_datetime_json, execute_datetime)
|
||||
|
||||
sut = SOQLBuilder(target_object, test_last_fetch_datetime)
|
||||
actual = sut.create_fetch_soql()
|
||||
|
||||
expect = """SELECT
|
||||
Id,
|
||||
Name,
|
||||
SystemModstamp,
|
||||
LastModifiedDate,
|
||||
CustomItem1__c,
|
||||
CustomItem2__c,
|
||||
CustomItem3__c,
|
||||
CustomItem4__c,
|
||||
CustomItem5__c,
|
||||
CustomItem6__c,
|
||||
CustomItem7__c,
|
||||
CustomItem8__c
|
||||
FROM
|
||||
Account
|
||||
WHERE
|
||||
SystemModstamp > 1999-01-01T00:00:00.000Z AND
|
||||
SystemModstamp <= 2000-01-01T00:00:00.000Z
|
||||
"""
|
||||
|
||||
print('actual', actual)
|
||||
print('expect', expect)
|
||||
|
||||
assert actual.replace('\n', '').replace(' ', '') == expect.replace('\n', '').replace(' ', '')
|
||||
|
||||
@pytest.mark.skip('データ取得用のSOQLがカラムがない状態は、TargetObjectクラス側で制御されるため、テストを実施しない')
|
||||
def test_raise_create_fetch_soql_cause_no_columns(self):
|
||||
"""
|
||||
Cases:
|
||||
データ取得用のSOQLがカラムがない状態で生成されること
|
||||
Arranges:
|
||||
SOQL生成用のパラメータを用意する
|
||||
Expects:
|
||||
取得対象のカラムがないSOQLが生成されること
|
||||
"""
|
||||
test_target_object_json = {
|
||||
'object_name': 'Account',
|
||||
'columns': []
|
||||
}
|
||||
|
||||
test_last_fetch_datetime_json = {
|
||||
'last_fetch_datetime_from': '1999-01-01T00:00:00.000Z',
|
||||
'last_fetch_datetime_to': '2000-01-01T00:00:00.000Z',
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(test_target_object_json, execute_datetime)
|
||||
test_last_fetch_datetime = LastFetchDatetime(test_last_fetch_datetime_json, execute_datetime)
|
||||
|
||||
sut = SOQLBuilder(target_object, test_last_fetch_datetime)
|
||||
actual = sut.create_fetch_soql()
|
||||
|
||||
# TargetObjectのバリデーションで、columnsが空の場合はエラーになるため、本来は発生しない
|
||||
expect = """SELECT
|
||||
FROM
|
||||
Account
|
||||
WHERE
|
||||
SystemModstamp > 1999-01-01T00:00:00.000Z AND
|
||||
SystemModstamp <= 2000-01-01T00:00:00.000Z
|
||||
"""
|
||||
|
||||
print('actual', actual)
|
||||
print('expect', expect)
|
||||
|
||||
assert actual.replace('\n', '').replace(' ', '') == expect.replace('\n', '').replace(' ', '')
|
||||
157
ecs/crm-datafetch/tests/test_backup_crm_csv_data_process.py
Normal file
157
ecs/crm-datafetch/tests/test_backup_crm_csv_data_process.py
Normal file
@ -0,0 +1,157 @@
|
||||
import textwrap
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from src.backup_crm_csv_data_process import backup_crm_csv_data_process
|
||||
from src.config.objects import TargetObject
|
||||
from src.error.exceptions import FileUploadException
|
||||
from src.system_var.constants import CSVBK_JP_NAME
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
from .test_utils.log_message import generate_log_message_tuple
|
||||
|
||||
|
||||
class TestBackupCrmCsvDataProcess:
|
||||
|
||||
@pytest.fixture
|
||||
def bucket_name(self):
|
||||
return 'test-backup-bucket'
|
||||
|
||||
@pytest.fixture
|
||||
def prepare_bucket(self, s3_client, bucket_name):
|
||||
s3_client.create_bucket(Bucket=bucket_name)
|
||||
yield
|
||||
|
||||
def test_run_process_success(self, s3_client, prepare_bucket, bucket_name, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
CSVバックアップ処理が正常終了し、期待通りの結果となること
|
||||
Arranges:
|
||||
- prepare_bucketフィクスチャで、CRM電文データをバックアップするためのモックバケットを作る
|
||||
- 作成したモックバケットを指すように環境変数を設定する
|
||||
Expects:
|
||||
- CSVバックアップファイルがバケットに配置される
|
||||
- CSVバックアップ処理の仕様に沿った正常系ログが出力されること(デバッグログは除く)
|
||||
"""
|
||||
# Arrange
|
||||
csv_string = textwrap.dedent("""\
|
||||
"Id","AccountNumber","LastModifiedDate","LastModifiedById","SystemModstamp","IsDeleted"
|
||||
"TEST001","test001","2022-06-01 09:00:00","1234567","2022-06-01 09:00:00","1"
|
||||
"TEST002","test002","2022-06-01 09:00:00","1234567","2022-06-01 09:00:00","0"
|
||||
"TEST003","test003","2022-06-01 09:00:00","1234567","2022-06-01 09:00:00","0"
|
||||
""")
|
||||
target_object_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'AccountNumber',
|
||||
'LastModifiedDate',
|
||||
'LastModifiedById',
|
||||
'SystemModstamp',
|
||||
'IsDeleted'
|
||||
]
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(target_object_dict, execute_datetime)
|
||||
|
||||
# 環境変数を編集
|
||||
monkeypatch.setattr('src.aws.s3.CRM_BACKUP_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.CRM_IMPORT_DATA_BACKUP_FOLDER', 'data_import')
|
||||
|
||||
# Act
|
||||
backup_crm_csv_data_process(target_object, execute_datetime, csv_string)
|
||||
|
||||
# Assert
|
||||
|
||||
# ファイル確認
|
||||
actual = s3_client.get_object(
|
||||
Bucket=bucket_name,
|
||||
Key=f'data_import/{execute_datetime.to_path()}/CRM_Account_{execute_datetime.format_date()}.csv')
|
||||
|
||||
assert actual['Body'].read().decode('utf-8') == csv_string
|
||||
|
||||
# ログの確認
|
||||
assert generate_log_message_tuple(
|
||||
log_message=f'I-CSVBK-01 [Account] のCSVデータのバックアップ処理を開始します ファイル名:[CRM_Account_{execute_datetime.format_date()}.csv]'
|
||||
) in caplog.record_tuples
|
||||
assert generate_log_message_tuple(
|
||||
log_message='I-CSVBK-03 [Account] のCSVデータのバックアップ処理を終了します') in caplog.record_tuples
|
||||
|
||||
def test_call_depended_modules(self):
|
||||
"""
|
||||
Cases:
|
||||
CSVバックアップ処理内で依存しているモジュールが正しく呼ばれていること
|
||||
Arranges:
|
||||
- CSVバックアップ処理の依存モジュールをモック化する
|
||||
Expects:
|
||||
- 依存しているモジュールが正しく呼ばれている
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
target_object_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'AccountNumber',
|
||||
'LastModifiedDate',
|
||||
'LastModifiedById',
|
||||
'SystemModstamp',
|
||||
'IsDeleted'
|
||||
]
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(target_object_dict, execute_datetime)
|
||||
|
||||
with patch('src.backup_crm_csv_data_process.BackupBucket') as mock_backup_bucket:
|
||||
mock_backup_bucket_inst = mock_backup_bucket.return_value
|
||||
mock_backup_bucket_inst.put_csv.return_value = ''
|
||||
# Act
|
||||
backup_crm_csv_data_process(target_object, execute_datetime, '')
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_backup_bucket_inst.put_csv.called is True
|
||||
|
||||
def test_raise_put_csv(self, bucket_name, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
CSVデータをバックアップできない場合、エラーが発生すること
|
||||
Arranges:
|
||||
- CSVデータをバックアップ処理で例外が発生するようにする
|
||||
Expects:
|
||||
- 例外が発生する
|
||||
- ファイル登録できないエラーが返却される
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
target_object_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'AccountNumber',
|
||||
'LastModifiedDate',
|
||||
'LastModifiedById',
|
||||
'SystemModstamp',
|
||||
'IsDeleted'
|
||||
]
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(target_object_dict, execute_datetime)
|
||||
|
||||
with patch('src.backup_crm_csv_data_process.BackupBucket') as mock_backup_bucket:
|
||||
mock_backup_bucket_inst = mock_backup_bucket.return_value
|
||||
mock_backup_bucket_inst.put_csv.side_effect = Exception('登録エラー')
|
||||
# Act
|
||||
with pytest.raises(FileUploadException) as e:
|
||||
backup_crm_csv_data_process(target_object, execute_datetime, '')
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_backup_bucket_inst.put_csv.called is True
|
||||
assert e.value.error_id == 'E-CSVBK-01'
|
||||
assert e.value.func_name == CSVBK_JP_NAME
|
||||
assert e.value.args[0] == \
|
||||
f'[Account] CSVデータのバックアップに失敗しました ファイル名:[CRM_Account_{execute_datetime.format_date()}.csv] エラー内容:[登録エラー]'
|
||||
186
ecs/crm-datafetch/tests/test_backup_crm_data_process.py
Normal file
186
ecs/crm-datafetch/tests/test_backup_crm_data_process.py
Normal file
@ -0,0 +1,186 @@
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from src.backup_crm_data_process import backup_crm_data_process
|
||||
from src.config.objects import TargetObject
|
||||
from src.error.exceptions import FileUploadException
|
||||
from src.system_var.constants import RESBK_JP_NAME
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
from .test_utils.log_message import generate_log_message_tuple
|
||||
|
||||
|
||||
class TestBackupCrmDataProcess:
|
||||
|
||||
@pytest.fixture
|
||||
def bucket_name(self):
|
||||
return 'test-backup-bucket'
|
||||
|
||||
@pytest.fixture
|
||||
def prepare_bucket(self, s3_client, bucket_name):
|
||||
s3_client.create_bucket(Bucket=bucket_name)
|
||||
yield
|
||||
|
||||
def test_run_process_success(self, s3_client, prepare_bucket, bucket_name, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
CRM電文データバックアップ処理が正常終了し、期待通りの結果となること
|
||||
Arranges:
|
||||
- prepare_bucketフィクスチャで、CRM電文データをバックアップするためのモックバケットを作る
|
||||
- 作成したモックバケットを指すように環境変数を設定する
|
||||
Expects:
|
||||
- CRM電文データバックアップファイルがバケットに配置される
|
||||
- CRM電文データバックアップ処理の仕様に沿った正常系ログが出力されること(デバッグログは除く)
|
||||
"""
|
||||
# Arrange
|
||||
response_json = [
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'Account'),
|
||||
('url', '/services/data/v1.0/sobjects/Account/TEST001')])),
|
||||
('Id', 'TEST001'),
|
||||
('AccountNumber', 'test001'),
|
||||
('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
|
||||
('LastModifiedById', 1.234567E+6),
|
||||
('SystemModstamp', '2022-06-01T00:00:00.000+0000'),
|
||||
('IsDeleted', False),
|
||||
('Name', 'テスト取引先1')
|
||||
]),
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'Account'),
|
||||
('url', '/services/data/v1.0/sobjects/Account/TEST002')])),
|
||||
('Id', 'TEST002'),
|
||||
('AccountNumber', 'test002'),
|
||||
('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
|
||||
('LastModifiedById', 1.234567E+6),
|
||||
('SystemModstamp', '2022-06-01T00:00:00.000+0000'),
|
||||
('IsDeleted', False),
|
||||
('Name', 'テスト取引先2')
|
||||
]),
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'Account'),
|
||||
('url', '/services/data/v1.0/sobjects/Account/TEST002')])),
|
||||
('Id', 'TEST002'),
|
||||
('AccountNumber', 'test002'),
|
||||
('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
|
||||
('LastModifiedById', 1.234567E+6),
|
||||
('SystemModstamp', '2022-06-01T00:00:00.000+0000'),
|
||||
('IsDeleted', False),
|
||||
('Name', 'テスト取引先3')
|
||||
]),
|
||||
]
|
||||
|
||||
target_object_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'AccountNumber',
|
||||
'LastModifiedDate',
|
||||
'LastModifiedById',
|
||||
'SystemModstamp',
|
||||
'IsDeleted'
|
||||
]
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(target_object_dict, execute_datetime)
|
||||
|
||||
# 環境変数を編集
|
||||
monkeypatch.setattr('src.aws.s3.CRM_BACKUP_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.RESPONSE_JSON_BACKUP_FOLDER', 'response_json')
|
||||
|
||||
# Act
|
||||
backup_crm_data_process(target_object, response_json, execute_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
# ファイル確認
|
||||
actual = s3_client.get_object(
|
||||
Bucket=bucket_name,
|
||||
Key=f'response_json/{execute_datetime.to_path()}/CRM_Account_{execute_datetime.format_date()}.json')
|
||||
assert actual['Body'].read().decode('utf-8') == json.dumps(response_json, ensure_ascii=False)
|
||||
|
||||
# ログの確認
|
||||
assert generate_log_message_tuple(
|
||||
log_message='I-RESBK-01 [Account] のCRM電文データバックアップ処理を開始します') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(
|
||||
log_message='I-RESBK-03 [Account] のCRM電文データバックアップ処理を終了します') in caplog.record_tuples
|
||||
|
||||
def test_call_depended_modules(self):
|
||||
"""
|
||||
Cases:
|
||||
CRM電文データバックアップ処理内で依存しているモジュールが正しく呼ばれていること
|
||||
Arranges:
|
||||
- CRM電文データバックアップ処理の依存モジュールをモック化する
|
||||
Expects:
|
||||
- 依存しているモジュールが正しく呼ばれている
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
target_object_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'AccountNumber',
|
||||
'LastModifiedDate',
|
||||
'LastModifiedById',
|
||||
'SystemModstamp',
|
||||
'IsDeleted'
|
||||
]
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(target_object_dict, execute_datetime)
|
||||
|
||||
with patch('src.backup_crm_data_process.BackupBucket') as mock_backup_bucket:
|
||||
mock_backup_bucket_inst = mock_backup_bucket.return_value
|
||||
mock_backup_bucket_inst.put_response_json.return_value = ''
|
||||
# Act
|
||||
backup_crm_data_process(target_object, {}, execute_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_backup_bucket_inst.put_response_json.called is True
|
||||
|
||||
def test_raise_put_response_json(self, bucket_name, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
CRM電文データをS3に配置できない場合、エラーが発生すること
|
||||
Arranges:
|
||||
- オブジェクト情報ファイル取得処理で例外が発生するようにする
|
||||
Expects:
|
||||
- 例外が発生する
|
||||
- ファイル登録できないエラーが返却される
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
target_object_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'AccountNumber',
|
||||
'LastModifiedDate',
|
||||
'LastModifiedById',
|
||||
'SystemModstamp',
|
||||
'IsDeleted'
|
||||
]
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(target_object_dict, execute_datetime)
|
||||
|
||||
with patch('src.backup_crm_data_process.BackupBucket') as mock_backup_bucket:
|
||||
mock_backup_bucket_inst = mock_backup_bucket.return_value
|
||||
mock_backup_bucket_inst.put_response_json.side_effect = Exception('登録エラー')
|
||||
# Act
|
||||
with pytest.raises(FileUploadException) as e:
|
||||
backup_crm_data_process(target_object, {}, execute_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_backup_bucket_inst.put_response_json.called is True
|
||||
assert e.value.error_id == 'E-RESBK-01'
|
||||
assert e.value.func_name == RESBK_JP_NAME
|
||||
assert e.value.args[0] == \
|
||||
f'[Account] 電文データのバックアップに失敗しました ファイル名:[CRM_Account_{execute_datetime.format_date()}.json] エラー内容:[登録エラー]'
|
||||
97
ecs/crm-datafetch/tests/test_check_object_info_process.py
Normal file
97
ecs/crm-datafetch/tests/test_check_object_info_process.py
Normal file
@ -0,0 +1,97 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from src.check_object_info_process import check_object_info_process
|
||||
from src.config.objects import TargetObject
|
||||
from src.error.exceptions import InvalidConfigException
|
||||
from src.system_var.constants import CHK_JP_NAME
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
from .test_utils.log_message import generate_log_message_tuple
|
||||
|
||||
|
||||
class TestCheckObjectInfoProcess:
|
||||
|
||||
def test_run_process_success(self, caplog):
|
||||
"""
|
||||
Cases:
|
||||
オブジェクト情報形式チェック処理が正常終了し、期待通りの結果が返ること
|
||||
Arranges:
|
||||
- チェック対象のdictオブジェクトを宣言する
|
||||
Expects:
|
||||
- チェック後のオブジェクト情報コレクションクラスのインスタンスが返却される
|
||||
- オブジェクト情報形式チェック処理の仕様に沿った正常系ログが出力されること(デバッグログは除く)
|
||||
"""
|
||||
# Arrange
|
||||
target_objects_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name'
|
||||
]
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
# Act
|
||||
actual_fetch_target_objects = check_object_info_process(target_objects_dict, execute_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
# 返り値の期待値チェック
|
||||
assert isinstance(actual_fetch_target_objects, TargetObject), 'CRM取得オブジェクトクラスのインスタンスが返却される'
|
||||
assert actual_fetch_target_objects.object_name == 'Account'
|
||||
assert actual_fetch_target_objects.columns == ['Id', 'Name']
|
||||
assert actual_fetch_target_objects.is_skip is False
|
||||
assert actual_fetch_target_objects.is_update_last_fetch_datetime is True
|
||||
assert actual_fetch_target_objects.datetime_column == 'SystemModstamp'
|
||||
assert actual_fetch_target_objects.upload_file_name == f'CRM_Account_{execute_datetime.format_date()}'
|
||||
assert actual_fetch_target_objects.last_fetch_datetime_file_name == 'Account.json'
|
||||
|
||||
# ログの確認
|
||||
assert generate_log_message_tuple(log_message='I-CHK-01 オブジェクト情報形式チェック処理を開始します') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message='I-CHK-02 オブジェクト情報形式チェック処理を終了します') in caplog.record_tuples
|
||||
|
||||
def test_call_depended_modules(self):
|
||||
"""
|
||||
Cases:
|
||||
オブジェクト情報形式チェック処理内で依存しているモジュールが正しく呼ばれていること
|
||||
Arranges:
|
||||
- オブジェクト情報形式チェック処理の依存モジュールをモック化する
|
||||
Expects:
|
||||
- 依存しているモジュールが正しく呼ばれている
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
mock_target_object_init = MagicMock(return_value=None)
|
||||
# Act
|
||||
with patch('src.config.objects.TargetObject.__init__', mock_target_object_init):
|
||||
check_object_info_process({}, {})
|
||||
|
||||
# Assert
|
||||
assert mock_target_object_init.called is True
|
||||
|
||||
def test_raise_check_object_info(self):
|
||||
"""
|
||||
Cases:
|
||||
オブジェクト形式チェック処理でエラーが発生した場合、検査例外が発生すること
|
||||
Arranges:
|
||||
- オブジェクト形式チェック処理で例外が発生するようにする
|
||||
Expects:
|
||||
- 例外が発生する
|
||||
- 形式チェックが失敗したエラーが返却される
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
mock_target_object_init = MagicMock(side_effect=Exception('形式チェックエラー'))
|
||||
# Act
|
||||
with patch('src.config.objects.TargetObject.__init__', mock_target_object_init):
|
||||
with pytest.raises(InvalidConfigException) as e:
|
||||
check_object_info_process({}, {})
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_target_object_init.called is True
|
||||
assert e.value.error_id == 'E-CHK-01'
|
||||
assert e.value.func_name == CHK_JP_NAME
|
||||
assert e.value.args[0] == f'オブジェクト情報形式チェック処理が失敗しました エラー内容:[形式チェックエラー]'
|
||||
905
ecs/crm-datafetch/tests/test_controller.py
Normal file
905
ecs/crm-datafetch/tests/test_controller.py
Normal file
@ -0,0 +1,905 @@
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from src import controller
|
||||
from src.config.objects import (FetchTargetObjects, LastFetchDatetime,
|
||||
TargetObject)
|
||||
from src.error.exceptions import MeDaCaCRMDataFetchException
|
||||
from src.system_var.constants import (CHK_JP_NAME, CONV_JP_NAME, CSVBK_JP_NAME,
|
||||
DATE_JP_NAME, END_JP_NAME, FETCH_JP_NAME,
|
||||
PRE_JP_NAME, RESBK_JP_NAME, UPD_JP_NAME,
|
||||
UPLD_JP_NAME)
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
from .test_utils.log_message import generate_log_message_tuple
|
||||
|
||||
COMMON_OBJECT_INFOS = {
|
||||
'objects': [
|
||||
{
|
||||
'object_name': 'Account',
|
||||
'columns': ['Id'],
|
||||
'upload_file_name': 'Account_YYYYMMDDHHMMSS'
|
||||
},
|
||||
{
|
||||
'object_name': 'Contact',
|
||||
'columns': ['Id'],
|
||||
'upload_file_name': 'Contact_YYYYMMDDHHMMSS'
|
||||
},
|
||||
{
|
||||
'object_name': 'Call2_vod__c',
|
||||
'columns': ['Id'],
|
||||
'upload_file_name': 'Call2_vod__c_YYYYMMDDHHMMSS'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
COMMON_EXECUTE_DATETIME = ExecuteDateTime()
|
||||
|
||||
COMMON_FETCH_TARGET_OBJECTS = FetchTargetObjects(COMMON_OBJECT_INFOS)
|
||||
|
||||
COMMON_TARGET_OBJECTS_1 = TargetObject(COMMON_OBJECT_INFOS['objects'][0], COMMON_EXECUTE_DATETIME)
|
||||
COMMON_TARGET_OBJECTS_2 = TargetObject(COMMON_OBJECT_INFOS['objects'][1], COMMON_EXECUTE_DATETIME)
|
||||
COMMON_TARGET_OBJECTS_3 = TargetObject(COMMON_OBJECT_INFOS['objects'][2], COMMON_EXECUTE_DATETIME)
|
||||
|
||||
COMMON_LAST_FETCH_DATETIME = LastFetchDatetime({
|
||||
'last_fetch_datetime_from': '1900-01-01T00:00:00.000Z',
|
||||
'last_fetch_datetime_to': ''
|
||||
}, COMMON_EXECUTE_DATETIME)
|
||||
|
||||
|
||||
class ForTestMeDaCaCRMDataFetchException(MeDaCaCRMDataFetchException):
|
||||
def __init__(self, error_id: str, func_name: str, message: str) -> None:
|
||||
super().__init__(error_id, func_name, message)
|
||||
|
||||
|
||||
class ForTestException(Exception):
|
||||
# カスタム例外とインタフェースを合わせるための引数
|
||||
def __init__(self, error_id: str, func_name: str, message: str) -> None:
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class TestController:
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_teardown(self):
|
||||
# setup
|
||||
self.mock_prepare_data_fetch_process = MagicMock()
|
||||
self.mock_check_object_info_process = MagicMock()
|
||||
self.mock_set_datetime_period_process = MagicMock()
|
||||
self.mock_fetch_crm_data_process = MagicMock()
|
||||
self.mock_backup_crm_data_process = MagicMock()
|
||||
self.mock_convert_crm_csv_data_process = MagicMock()
|
||||
self.mock_backup_crm_csv_data_process = MagicMock()
|
||||
self.mock_copy_crm_csv_data_process = MagicMock()
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock()
|
||||
self.mock_upload_result_data_process = MagicMock()
|
||||
|
||||
# run test
|
||||
yield
|
||||
|
||||
# teardown
|
||||
self.mock_prepare_data_fetch_process.reset_mock()
|
||||
self.mock_check_object_info_process.reset_mock()
|
||||
self.mock_set_datetime_period_process.reset_mock()
|
||||
self.mock_fetch_crm_data_process.reset_mock()
|
||||
self.mock_backup_crm_data_process.reset_mock()
|
||||
self.mock_convert_crm_csv_data_process.reset_mock()
|
||||
self.mock_backup_crm_csv_data_process.reset_mock()
|
||||
self.mock_copy_crm_csv_data_process.reset_mock()
|
||||
self.mock_upload_last_fetch_datetime_process.reset_mock()
|
||||
self.mock_upload_result_data_process.reset_mock()
|
||||
|
||||
@pytest.fixture()
|
||||
def run_control_process(self):
|
||||
|
||||
def _func():
|
||||
with patch('src.controller.prepare_data_fetch_process', self.mock_prepare_data_fetch_process),\
|
||||
patch('src.controller.check_object_info_process', self.mock_check_object_info_process),\
|
||||
patch('src.controller.set_datetime_period_process', self.mock_set_datetime_period_process),\
|
||||
patch('src.controller.fetch_crm_data_process', self.mock_fetch_crm_data_process),\
|
||||
patch('src.controller.backup_crm_data_process', self.mock_backup_crm_data_process),\
|
||||
patch('src.controller.convert_crm_csv_data_process', self.mock_convert_crm_csv_data_process),\
|
||||
patch('src.controller.backup_crm_csv_data_process', self.mock_backup_crm_csv_data_process),\
|
||||
patch('src.controller.copy_crm_csv_data_process', self.mock_copy_crm_csv_data_process),\
|
||||
patch('src.controller.upload_last_fetch_datetime_process', self.mock_upload_last_fetch_datetime_process),\
|
||||
patch('src.controller.upload_result_data_process', self.mock_upload_result_data_process):
|
||||
controller.controller()
|
||||
yield _func
|
||||
|
||||
@pytest.fixture()
|
||||
def call_all_processes(self, caplog, run_control_process):
|
||||
"""
|
||||
コントロール処理内ですべてのプロセス関数が呼ばれることのテストで使用するフィクスチャ
|
||||
各種プロセス関数をモック化し、正常終了させるように動く
|
||||
|
||||
Yields:
|
||||
dict: プロセス関数呼び出し後のモックの辞書
|
||||
"""
|
||||
def _func():
|
||||
mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3]
|
||||
self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {}))
|
||||
self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values)
|
||||
self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME)
|
||||
self.mock_fetch_crm_data_process = MagicMock(return_value=[{'Name': 'Test'}])
|
||||
self.mock_backup_crm_data_process = MagicMock()
|
||||
self.mock_convert_crm_csv_data_process = MagicMock()
|
||||
self.mock_backup_crm_csv_data_process = MagicMock()
|
||||
self.mock_copy_crm_csv_data_process = MagicMock()
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock()
|
||||
self.mock_upload_result_data_process = MagicMock()
|
||||
|
||||
run_control_process()
|
||||
|
||||
yield _func
|
||||
|
||||
def test_call_all_processes(self, call_all_processes):
|
||||
"""
|
||||
Cases:
|
||||
コントロール処理からすべてのプロセスが呼ばれること
|
||||
Arranges:
|
||||
- call_all_processesフィクスチャで、コントロール処理を実行しておく
|
||||
Expects:
|
||||
- データ取得準備処理が1回のみ実行される
|
||||
- オブジェクト情報形式チェック処理が複数回実行される
|
||||
- データ取得期間設定処理が複数回実行される
|
||||
- CRMデータ取得処理が複数回実行される
|
||||
- CRM電文データバックアップ処理が複数回実行される
|
||||
- CSV変換処理が複数回実行される
|
||||
- CSVバックアップ処理が複数回実行される
|
||||
- CSVアップロード処理が複数回実行される
|
||||
- 前回取得日時ファイル更新処理が複数回実行される
|
||||
- 取得処理実施結果アップロード処理が1回のみ実行される
|
||||
"""
|
||||
call_all_processes()
|
||||
assert self.mock_prepare_data_fetch_process.called is True, 'データ取得準備処理が1回のみ実行されること'
|
||||
assert self.mock_prepare_data_fetch_process.call_count == 1, 'データ取得準備処理が1回のみ実行されること'
|
||||
assert self.mock_check_object_info_process.call_count == 3, 'オブジェクト情報形式チェック処理が複数回実行されること'
|
||||
assert self.mock_set_datetime_period_process.call_count == 3, 'データ取得期間設定処理が複数回実行されること'
|
||||
assert self.mock_fetch_crm_data_process.call_count == 3, 'CRMデータ取得処理が複数回実行されること'
|
||||
assert self.mock_backup_crm_data_process.call_count == 3, 'CRM電文データバックアップ処理が複数回実行されること'
|
||||
assert self.mock_convert_crm_csv_data_process.call_count == 3, 'CSV変換処理が複数回実行されること'
|
||||
assert self.mock_backup_crm_csv_data_process.call_count == 3, 'CSVバックアップ処理が複数回実行されること'
|
||||
assert self.mock_copy_crm_csv_data_process.call_count == 3, 'CSVアップロード処理が複数回実行されること'
|
||||
assert self.mock_upload_last_fetch_datetime_process.call_count == 3, '前回取得日時ファイル更新処理が複数回実行されること'
|
||||
assert self.mock_upload_result_data_process.called is True, '取得処理実施結果アップロード処理が1回のみ実行されること'
|
||||
assert self.mock_upload_result_data_process.call_count == 1, '取得処理実施結果アップロード処理が1回のみ実行されること'
|
||||
|
||||
def test_print_normal_logs(self, call_all_processes, caplog):
|
||||
"""
|
||||
Cases:
|
||||
コントロール処理の正常系ログがすべての出力されていること
|
||||
Arranges:
|
||||
- call_all_processesフィクスチャで、コントロール処理を実行しておく
|
||||
Expects:
|
||||
- コントロール処理の正常系ログがすべて出力されている
|
||||
"""
|
||||
call_all_processes()
|
||||
|
||||
assert generate_log_message_tuple(log_message='I-CTRL-01 CRMデータ取得処理を開始します') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message='I-CTRL-02 データ取得準備処理呼び出し') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message='I-CTRL-03 取得対象オブジェクトのループ処理開始') in caplog.record_tuples
|
||||
for name in ['Account', 'Contact', 'Call2_vod__c']:
|
||||
object_name = name
|
||||
upload_file_name = f'{name}_YYYYMMDDHHMMSS'
|
||||
assert generate_log_message_tuple(log_message='I-CTRL-05 オブジェクト情報形式チェック処理呼び出し') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-06 [{object_name}]のデータ取得を開始します') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-08 [{object_name}]のデータ取得期間設定処理呼び出し') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-09 [{object_name}]のデータ取得処理呼び出し') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-10 [{object_name}] の出力ファイル名は [{upload_file_name}] となります') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-11 [{object_name}] CRM電文データバックアップ処理呼び出し') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-12 [{object_name}] CSV変換処理呼び出し') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-13 [{object_name}] CSVデータバックアップ処理呼び出し') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-14 [{object_name}] CSVデータアップロード処理呼び出し') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-15 [{object_name}] 前回取得日時ファイル更新処理呼び出し') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-16 [{object_name}] 処理正常終了') in caplog.record_tuples
|
||||
|
||||
expect_process_result = {
|
||||
'Account': 'success',
|
||||
'Contact': 'success',
|
||||
'Call2_vod__c': 'success'
|
||||
}
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-18 CRM_取得処理実施結果ファイルアップロード処理開始') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-19 すべてのデータの取得に成功しました') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-20 CRMデータ取得処理を終了します') in caplog.record_tuples
|
||||
|
||||
def test_do_not_call_upload_process_result_process(self, caplog, run_control_process):
|
||||
"""
|
||||
Cases:
|
||||
処理対象オブジェクトが0件の場合、取得処理実施結果アップロード処理が実行されないこと
|
||||
Arranges:
|
||||
- データ取得準備処理で返される取得対象オブジェクト情報を0件にする
|
||||
- 各種プロセスメソッドと内部で使用している値オブジェクトをモック化する
|
||||
Expects:
|
||||
- データ取得準備処理が1回のみ実行されること
|
||||
- 取得処理実施結果アップロード処理が実行されないこと
|
||||
- 処理対象が存在しない旨を示すログメッセージが出力されていること
|
||||
"""
|
||||
|
||||
self.mock_prepare_data_fetch_process = MagicMock(return_value=([], COMMON_EXECUTE_DATETIME, {}))
|
||||
self.mock_check_object_info_process = MagicMock()
|
||||
self.mock_set_datetime_period_process = MagicMock()
|
||||
self.mock_fetch_crm_data_process = MagicMock()
|
||||
self.mock_backup_crm_data_process = MagicMock()
|
||||
self.mock_convert_crm_csv_data_process = MagicMock()
|
||||
self.mock_backup_crm_csv_data_process = MagicMock()
|
||||
self.mock_copy_crm_csv_data_process = MagicMock()
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock()
|
||||
self.mock_upload_result_data_process = MagicMock()
|
||||
|
||||
run_control_process()
|
||||
|
||||
# 実行回数の確認
|
||||
assert self.mock_prepare_data_fetch_process.called, 'データ取得準備処理が1回のみ実行されること'
|
||||
assert self.mock_prepare_data_fetch_process.call_count == 1, 'データ取得準備処理が1回のみ実行されること'
|
||||
assert self.mock_upload_result_data_process.called is False, '取得処理実施結果アップロード処理が実行されないこと'
|
||||
assert self.mock_upload_result_data_process.call_count == 0, '取得処理実施結果アップロード処理が実行されないこと'
|
||||
|
||||
# ログ出力の確認
|
||||
assert generate_log_message_tuple(log_message='I-CTRL-21 処理対象のデータが存在しませんでした') in caplog.record_tuples, '処理対象が存在しない旨を示すログメッセージが出力されていること'
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-20 CRMデータ取得処理を終了します') in caplog.record_tuples
|
||||
|
||||
def test_do_not_call_upload_csv_process_cause_is_skip_true(self, caplog, run_control_process):
|
||||
"""
|
||||
Cases:
|
||||
オブジェクト情報.is_skipがTrueの場合、CSVアップロード処理が実行されないこと
|
||||
Arranges:
|
||||
- データ取得準備処理で返される取得対象オブジェクト情報のis_skipをTrueにする
|
||||
- 各種プロセスメソッドと内部で使用している値オブジェクトをモック化する
|
||||
Expects:
|
||||
- オブジェクト情報形式チェック処理以降のプロセスが実行されないこと
|
||||
- 処理をスキップする旨を示すログメッセージが出力されていること
|
||||
"""
|
||||
mock_check_object_info = {
|
||||
'object_name': 'Account',
|
||||
'columns': ['id'],
|
||||
'is_skip': True
|
||||
}
|
||||
mock_return_values = [TargetObject(mock_check_object_info, COMMON_EXECUTE_DATETIME)]
|
||||
self.mock_prepare_data_fetch_process = MagicMock(return_value=([mock_check_object_info], COMMON_EXECUTE_DATETIME, {}))
|
||||
self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values)
|
||||
self.mock_set_datetime_period_process = MagicMock()
|
||||
self.mock_fetch_crm_data_process = MagicMock()
|
||||
self.mock_backup_crm_data_process = MagicMock()
|
||||
self.mock_convert_crm_csv_data_process = MagicMock()
|
||||
self.mock_backup_crm_csv_data_process = MagicMock()
|
||||
self.mock_copy_crm_csv_data_process = MagicMock()
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock()
|
||||
self.mock_upload_result_data_process = MagicMock()
|
||||
|
||||
run_control_process()
|
||||
|
||||
# 実行回数の確認
|
||||
|
||||
assert self.mock_check_object_info_process.called is True
|
||||
assert self.mock_check_object_info_process.call_count == 1
|
||||
# オブジェクト情報形式チェック処理以降のプロセスが実行されないこと
|
||||
assert self.mock_set_datetime_period_process.call_count == 0
|
||||
assert self.mock_fetch_crm_data_process.call_count == 0
|
||||
assert self.mock_backup_crm_data_process.call_count == 0
|
||||
assert self.mock_convert_crm_csv_data_process.call_count == 0
|
||||
assert self.mock_backup_crm_csv_data_process.call_count == 0
|
||||
assert self.mock_copy_crm_csv_data_process.call_count == 0
|
||||
assert self.mock_upload_last_fetch_datetime_process.call_count == 0
|
||||
# 結果ファイルの出力は行う
|
||||
assert self.mock_upload_result_data_process.called is True
|
||||
assert self.mock_upload_result_data_process.call_count == 1
|
||||
|
||||
# ログ出力の確認
|
||||
assert generate_log_message_tuple(log_message='I-CTRL-07 [Account]のデータ取得処理をスキップします') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-20 CRMデータ取得処理を終了します') in caplog.record_tuples
|
||||
|
||||
def test_do_not_call_upload_csv_process_cause_is_skip_true_in_loop(self, caplog, run_control_process):
|
||||
"""
|
||||
Cases:
|
||||
オブジェクト情報.is_skipがTrueのものが含まれる場合、対象オブジェクトのCSVアップロード処理が実行されないこと
|
||||
Arranges:
|
||||
- データ取得準備処理で返される取得対象オブジェクト情報のうち、2つ目をis_skipをTrueにする
|
||||
- 各種プロセスメソッドと内部で使用している値オブジェクトをモック化する
|
||||
Expects:
|
||||
- オブジェクト情報形式チェック処理が2回実行されること
|
||||
- 処理をスキップする旨を示すログメッセージが出力されていること
|
||||
- オブジェクト情報.is_skipがFalseのものはCSVアップロード処理のログメッセージが出力されていること
|
||||
"""
|
||||
mock_check_object_infos = {
|
||||
'objects': [
|
||||
{
|
||||
'object_name': 'Account',
|
||||
'columns': ['id'],
|
||||
'is_skip': False
|
||||
},
|
||||
{
|
||||
'object_name': 'Contact',
|
||||
'columns': ['id'],
|
||||
'is_skip': True
|
||||
},
|
||||
{
|
||||
'object_name': 'Call2_vod__c',
|
||||
'columns': ['id'],
|
||||
'is_skip': False
|
||||
}
|
||||
]
|
||||
}
|
||||
FetchTargetObjects(mock_check_object_infos)
|
||||
mock_return_values = [
|
||||
TargetObject(mock_check_object_infos['objects'][0], COMMON_EXECUTE_DATETIME),
|
||||
TargetObject(mock_check_object_infos['objects'][1], COMMON_EXECUTE_DATETIME),
|
||||
TargetObject(mock_check_object_infos['objects'][2], COMMON_EXECUTE_DATETIME),
|
||||
]
|
||||
self.mock_prepare_data_fetch_process = MagicMock(return_value=(FetchTargetObjects(mock_check_object_infos), COMMON_EXECUTE_DATETIME, {}))
|
||||
self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values)
|
||||
self.mock_set_datetime_period_process = MagicMock()
|
||||
self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{"Name": "Test"}], [{"Name": "Test"}]])
|
||||
self.mock_backup_crm_data_process = MagicMock()
|
||||
self.mock_convert_crm_csv_data_process = MagicMock()
|
||||
self.mock_backup_crm_csv_data_process = MagicMock()
|
||||
self.mock_copy_crm_csv_data_process = MagicMock()
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock()
|
||||
self.mock_upload_result_data_process = MagicMock()
|
||||
|
||||
run_control_process()
|
||||
# 実行回数の確認
|
||||
assert self.mock_prepare_data_fetch_process.called is True
|
||||
assert self.mock_prepare_data_fetch_process.call_count == 1
|
||||
assert self.mock_check_object_info_process.call_count == 3
|
||||
# オブジェクト情報形式チェック処理以降のプロセスが実行されないこと
|
||||
assert self.mock_set_datetime_period_process.call_count == 2
|
||||
assert self.mock_fetch_crm_data_process.call_count == 2
|
||||
assert self.mock_backup_crm_data_process.call_count == 2
|
||||
assert self.mock_convert_crm_csv_data_process.call_count == 2
|
||||
assert self.mock_backup_crm_csv_data_process.call_count == 2
|
||||
assert self.mock_copy_crm_csv_data_process.call_count == 2
|
||||
assert self.mock_upload_last_fetch_datetime_process.call_count == 2
|
||||
assert self.mock_upload_result_data_process.called is True
|
||||
assert self.mock_upload_result_data_process.call_count == 1
|
||||
|
||||
# ログ出力の確認
|
||||
assert generate_log_message_tuple(
|
||||
log_message='I-CTRL-14 [Account] CSVデータアップロード処理呼び出し') in caplog.record_tuples, 'オブジェクト情報.is_skipがFalseのものはCSVアップロード処理のログメッセージが出力されていること'
|
||||
assert generate_log_message_tuple(
|
||||
log_message='I-CTRL-07 [Contact]のデータ取得処理をスキップします') in caplog.record_tuples, '処理をスキップする旨を示すログメッセージが出力されていること'
|
||||
assert generate_log_message_tuple(
|
||||
log_message='I-CTRL-14 [Call2_vod__c] CSVデータアップロード処理呼び出し'
|
||||
) in caplog.record_tuples, 'オブジェクト情報.is_skipがFalseのものはCSVアップロード処理のログメッセージが出力されていること'
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-20 CRMデータ取得処理を終了します') in caplog.record_tuples
|
||||
|
||||
def test_do_not_call_upload_csv_process_cause_crm_data_empty(self, caplog, run_control_process):
|
||||
"""
|
||||
Cases:
|
||||
CRMデータ取得処理で取得できた件数が0件の場合、CSVアップロード処理が実行されないこと
|
||||
Arranges:
|
||||
- CRMデータ取得処理で返されるオブジェクトを空のリストにする
|
||||
- 各種プロセスメソッドと内部で使用している値オブジェクトをモック化する
|
||||
Expects:
|
||||
- CRM電文データバックアップ処理以降のプロセスが実行されないこと
|
||||
- 処理をスキップする旨を示すログメッセージが出力されていること
|
||||
"""
|
||||
mock_check_object_infos = {
|
||||
'objects': [
|
||||
{
|
||||
'object_name': 'Account',
|
||||
'columns': ['id'],
|
||||
'is_skip': False
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
FetchTargetObjects(mock_check_object_infos)
|
||||
mock_return_values = [
|
||||
TargetObject(mock_check_object_infos['objects'][0], COMMON_EXECUTE_DATETIME)
|
||||
]
|
||||
self.mock_prepare_data_fetch_process = MagicMock(return_value=(FetchTargetObjects(mock_check_object_infos), COMMON_EXECUTE_DATETIME, {}))
|
||||
self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values)
|
||||
self.mock_set_datetime_period_process = MagicMock()
|
||||
self.mock_fetch_crm_data_process = MagicMock(return_value=[])
|
||||
self.mock_backup_crm_data_process = MagicMock()
|
||||
self.mock_convert_crm_csv_data_process = MagicMock()
|
||||
self.mock_backup_crm_csv_data_process = MagicMock()
|
||||
self.mock_copy_crm_csv_data_process = MagicMock()
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock()
|
||||
self.mock_upload_result_data_process = MagicMock()
|
||||
|
||||
run_control_process()
|
||||
# 実行回数の確認
|
||||
assert self.mock_prepare_data_fetch_process.called is True
|
||||
assert self.mock_prepare_data_fetch_process.call_count == 1
|
||||
assert self.mock_check_object_info_process.call_count == 1
|
||||
assert self.mock_set_datetime_period_process.call_count == 1
|
||||
assert self.mock_fetch_crm_data_process.call_count == 1
|
||||
# CRM電文データバックアップ処理以降のプロセスが実行されないこと
|
||||
assert self.mock_backup_crm_data_process.call_count == 0
|
||||
assert self.mock_convert_crm_csv_data_process.call_count == 0
|
||||
assert self.mock_backup_crm_csv_data_process.call_count == 0
|
||||
assert self.mock_copy_crm_csv_data_process.call_count == 0
|
||||
assert self.mock_upload_last_fetch_datetime_process.call_count == 0
|
||||
assert self.mock_upload_result_data_process.called is True
|
||||
assert self.mock_upload_result_data_process.call_count == 1
|
||||
|
||||
# ログ出力の確認
|
||||
assert generate_log_message_tuple(
|
||||
log_message='I-CTRL-22 [Account]のレコード件数が0件のため、ファイルアップロードをスキップします') in caplog.record_tuples, '処理をスキップする旨を示すログメッセージが出力されていること'
|
||||
|
||||
def test_do_not_call_upload_csv_process_cause_crm_data_empty_in_loop(self, caplog, run_control_process):
|
||||
"""
|
||||
Cases:
|
||||
CRMデータ取得処理で取得できた件数が0件のものが含まれる場合、対象オブジェクトのCSVアップロード処理が実行されないこと
|
||||
Arranges:
|
||||
- CRMデータ取得処理で返されるオブジェクトのうち、2つ目を空のリストにする
|
||||
- 各種プロセスメソッドと内部で使用している値オブジェクトをモック化する
|
||||
Expects:
|
||||
- CRMデータ取得処理が2回実行されること
|
||||
- 処理をスキップする旨を示すログメッセージが出力されていること
|
||||
- 取得オブジェクトが1件以上取れているものはCSVアップロード処理のログメッセージが出力されていること
|
||||
"""
|
||||
self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {}))
|
||||
self.mock_check_object_info_process = MagicMock(side_effect=[COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3])
|
||||
self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME)
|
||||
self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{"Name": "Test"}], [], [{"Name": "Test"}]])
|
||||
self.mock_backup_crm_data_process = MagicMock()
|
||||
self.mock_convert_crm_csv_data_process = MagicMock()
|
||||
self.mock_backup_crm_csv_data_process = MagicMock()
|
||||
self.mock_copy_crm_csv_data_process = MagicMock()
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock()
|
||||
self.mock_upload_result_data_process = MagicMock()
|
||||
|
||||
run_control_process()
|
||||
# 実行回数の確認
|
||||
assert self.mock_check_object_info_process.call_count == 3
|
||||
assert self.mock_set_datetime_period_process.call_count == 3
|
||||
assert self.mock_fetch_crm_data_process.call_count == 3
|
||||
# CRM電文データバックアップ処理以降のプロセスは件数があるもののみ実行されること
|
||||
assert self.mock_backup_crm_data_process.call_count == 2
|
||||
assert self.mock_convert_crm_csv_data_process.call_count == 2
|
||||
assert self.mock_backup_crm_csv_data_process.call_count == 2
|
||||
assert self.mock_copy_crm_csv_data_process.call_count == 2
|
||||
assert self.mock_upload_last_fetch_datetime_process.call_count == 2
|
||||
assert self.mock_upload_result_data_process.called is True
|
||||
assert self.mock_upload_result_data_process.call_count == 1
|
||||
|
||||
# ログ出力の確認
|
||||
assert generate_log_message_tuple(
|
||||
log_message='I-CTRL-14 [Account] CSVデータアップロード処理呼び出し') in caplog.record_tuples, '取得オブジェクトが1件以上取れているものはCSVアップロード処理のログメッセージが出力されていること'
|
||||
assert generate_log_message_tuple(
|
||||
log_message='I-CTRL-22 [Contact]のレコード件数が0件のため、ファイルアップロードをスキップします') in caplog.record_tuples, '処理をスキップする旨を示すログメッセージが出力されていること'
|
||||
assert generate_log_message_tuple(
|
||||
log_message='I-CTRL-14 [Call2_vod__c] CSVデータアップロード処理呼び出し'
|
||||
) in caplog.record_tuples, '取得オブジェクトが1件以上取れているものはCSVアップロード処理のログメッセージが出力されていること'
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'exception, message',
|
||||
[
|
||||
(ForTestMeDaCaCRMDataFetchException, f'E-ERR-01 [{PRE_JP_NAME}]でエラーが発生したため、処理を終了します'),
|
||||
(ForTestException, 'E-ERR-02 予期せぬエラーが発生したため、処理を終了します エラー内容: [例外発生]')
|
||||
])
|
||||
def test_raise_prepare_data_fetch_process(self, caplog, exception, message, run_control_process):
|
||||
"""
|
||||
Cases:
|
||||
1. データ取得準備処理でシステム例外が発生した場合、エラーで終了すること
|
||||
2. データ取得準備処理で想定外の例外が発生した場合、エラーで終了すること
|
||||
Arranges:
|
||||
- パラメータ1:データ取得準備処理でシステム例外が発生するようにする
|
||||
- パラメータ2:データ取得準備処理で想定外の例外が発生するようにする
|
||||
Expects:
|
||||
- データ取得準備処理で例外が発生すること
|
||||
- データ取得準備処理で発生した例外のログメッセージが出力されていること
|
||||
"""
|
||||
expect_exception = exception('E-PRE-01', PRE_JP_NAME, '例外発生')
|
||||
self.mock_prepare_data_fetch_process = MagicMock(side_effect=expect_exception)
|
||||
self.mock_check_object_info_process = MagicMock()
|
||||
with pytest.raises(exception):
|
||||
run_control_process()
|
||||
assert self.mock_check_object_info_process.called is False
|
||||
assert generate_log_message_tuple(
|
||||
log_level=logging.ERROR,
|
||||
log_message=message) in caplog.record_tuples, 'データ取得準備処理で発生した例外のログメッセージが出力されていること'
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'exception, message',
|
||||
[
|
||||
(ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{CHK_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'),
|
||||
(ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]')
|
||||
])
|
||||
def test_raise_check_object_info_process(self, caplog, exception, message, run_control_process):
|
||||
"""
|
||||
Cases:
|
||||
1. オブジェクト情報形式チェック処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
2. オブジェクト情報形式チェック処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
Arranges:
|
||||
- パラメータ1:オブジェクト情報形式チェック処理でシステム例外が発生するようにする
|
||||
- パラメータ2:オブジェクト情報形式チェック処理で想定外の例外が発生するようにする
|
||||
Expects:
|
||||
- オブジェクト情報形式チェック処理で例外が発生する
|
||||
- オブジェクト情報形式チェック処理で発生した例外のログメッセージが出力されている
|
||||
- 例外が発生したオブジェクト以外は正常に終了する
|
||||
"""
|
||||
raise_object = exception('E-PRE-01', CHK_JP_NAME, '例外発生')
|
||||
mock_return_values = [raise_object, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_2]
|
||||
|
||||
self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {}))
|
||||
self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values)
|
||||
self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME)
|
||||
self.mock_fetch_crm_data_process = MagicMock(return_value=[{'Name': 'Test'}])
|
||||
self.mock_backup_crm_data_process = MagicMock()
|
||||
self.mock_convert_crm_csv_data_process = MagicMock()
|
||||
self.mock_backup_crm_csv_data_process = MagicMock()
|
||||
self.mock_copy_crm_csv_data_process = MagicMock()
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock()
|
||||
self.mock_upload_result_data_process = MagicMock()
|
||||
|
||||
run_control_process()
|
||||
assert self.mock_check_object_info_process.call_count == 3
|
||||
assert self.mock_set_datetime_period_process.call_count == 2
|
||||
expect_process_result = {
|
||||
'Account': 'fail',
|
||||
'Contact': 'success',
|
||||
'Call2_vod__c': 'success'
|
||||
}
|
||||
assert generate_log_message_tuple(log_message=message) in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(
|
||||
log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'exception, message',
|
||||
[
|
||||
(ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{DATE_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'),
|
||||
(ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]')
|
||||
])
|
||||
def test_raise_set_datetime_period_process(self, caplog, exception, message, run_control_process):
|
||||
"""
|
||||
Cases:
|
||||
1. データ取得期間設定処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
2. データ取得期間設定処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
Arranges:
|
||||
- パラメータ1:データ取得期間設定処理でシステム例外が発生するようにする
|
||||
- パラメータ2:データ取得期間設定処理で想定外の例外が発生するようにする
|
||||
Expects:
|
||||
- データ取得期間設定チェック処理で例外が発生する
|
||||
- データ取得期間設定チェック処理で発生した例外のログメッセージが出力されている
|
||||
- 例外が発生したオブジェクト以外は正常に終了する
|
||||
"""
|
||||
mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3]
|
||||
|
||||
self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {}))
|
||||
self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values)
|
||||
self.mock_set_datetime_period_process = MagicMock(side_effect=[exception(
|
||||
'E-DATE-01', DATE_JP_NAME, '例外発生'), COMMON_LAST_FETCH_DATETIME, COMMON_LAST_FETCH_DATETIME])
|
||||
self.mock_fetch_crm_data_process = MagicMock(return_value=[{'Name': 'Test'}])
|
||||
self.mock_backup_crm_data_process = MagicMock()
|
||||
self.mock_convert_crm_csv_data_process = MagicMock()
|
||||
self.mock_backup_crm_csv_data_process = MagicMock()
|
||||
self.mock_copy_crm_csv_data_process = MagicMock()
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock()
|
||||
self.mock_upload_result_data_process = MagicMock()
|
||||
|
||||
run_control_process()
|
||||
|
||||
assert self.mock_set_datetime_period_process.call_count == 3
|
||||
assert self.mock_fetch_crm_data_process.call_count == 2
|
||||
expect_process_result = {
|
||||
'Account': 'fail',
|
||||
'Contact': 'success',
|
||||
'Call2_vod__c': 'success'
|
||||
}
|
||||
assert generate_log_message_tuple(log_message=message) in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(
|
||||
log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'exception, message',
|
||||
[
|
||||
(ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{FETCH_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'),
|
||||
(ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]')
|
||||
])
|
||||
def test_raise_fetch_crm_data_process(self, caplog, exception, message, run_control_process):
|
||||
"""
|
||||
Cases:
|
||||
1. CRMデータ取得処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
2. CRMデータ取得処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
Arranges:
|
||||
- パラメータ1:CRMデータ取得処理でシステム例外が発生するようにする
|
||||
- パラメータ2:CRMデータ取得処理で想定外の例外が発生するようにする
|
||||
Expects:
|
||||
- CRMデータ取得処理で例外が発生すること
|
||||
- CRMデータ取得処理で発生した例外のログメッセージが出力されていること
|
||||
"""
|
||||
mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3]
|
||||
|
||||
self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {}))
|
||||
self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values)
|
||||
self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME)
|
||||
self.mock_fetch_crm_data_process = MagicMock(side_effect=[exception(
|
||||
'E-FETCH-01', FETCH_JP_NAME, '例外発生'), [{'Name': 'Test'}], [{'Name': 'Test'}]])
|
||||
self.mock_backup_crm_data_process = MagicMock()
|
||||
self.mock_convert_crm_csv_data_process = MagicMock()
|
||||
self.mock_backup_crm_csv_data_process = MagicMock()
|
||||
self.mock_copy_crm_csv_data_process = MagicMock()
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock()
|
||||
self.mock_upload_result_data_process = MagicMock()
|
||||
|
||||
run_control_process()
|
||||
|
||||
assert self.mock_fetch_crm_data_process.call_count == 3
|
||||
assert self.mock_backup_crm_data_process.call_count == 2
|
||||
expect_process_result = {
|
||||
'Account': 'fail',
|
||||
'Contact': 'success',
|
||||
'Call2_vod__c': 'success'
|
||||
}
|
||||
assert generate_log_message_tuple(log_message=message) in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(
|
||||
log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'exception, message',
|
||||
[
|
||||
(ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{RESBK_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'),
|
||||
(ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]')
|
||||
])
|
||||
def test_raise_backup_crm_data_process(self, caplog, exception, message, run_control_process):
|
||||
"""
|
||||
Cases:
|
||||
1. CRM電文データバックアップ処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
2. CRM電文データバックアップ処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
Arranges:
|
||||
- パラメータ1:CRM電文データバックアップ処理でシステム例外が発生するようにする
|
||||
- パラメータ2:CRM電文データバックアップ処理で想定外の例外が発生するようにする
|
||||
Expects:
|
||||
- CRM電文データバックアップ処理で例外が発生すること
|
||||
- CRM電文データバックアップ処理で発生した例外のログメッセージが出力されていること
|
||||
"""
|
||||
mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3]
|
||||
|
||||
self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {}))
|
||||
self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values)
|
||||
self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME)
|
||||
self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{'Name': 'Test'}], [{'Name': 'Test'}], [{'Name': 'Test'}]])
|
||||
self.mock_backup_crm_data_process = MagicMock(side_effect=[exception(
|
||||
'E-RESBK-01', RESBK_JP_NAME, '例外発生'), None, None])
|
||||
self.mock_convert_crm_csv_data_process = MagicMock()
|
||||
self.mock_backup_crm_csv_data_process = MagicMock()
|
||||
self.mock_copy_crm_csv_data_process = MagicMock()
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock()
|
||||
self.mock_upload_result_data_process = MagicMock()
|
||||
|
||||
run_control_process()
|
||||
|
||||
assert self.mock_backup_crm_data_process.call_count == 3
|
||||
assert self.mock_convert_crm_csv_data_process.call_count == 2
|
||||
expect_process_result = {
|
||||
'Account': 'fail',
|
||||
'Contact': 'success',
|
||||
'Call2_vod__c': 'success'
|
||||
}
|
||||
assert generate_log_message_tuple(log_message=message) in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(
|
||||
log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'exception, message',
|
||||
[
|
||||
(ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{CONV_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'),
|
||||
(ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]')
|
||||
])
|
||||
def test_raise_convert_crm_csv_data_process(self, caplog, exception, message, run_control_process):
|
||||
"""
|
||||
Cases:
|
||||
1. CSV変換処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
2. CSV変換処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
Arranges:
|
||||
- パラメータ1:CSV変換処理でシステム例外が発生するようにする
|
||||
- パラメータ2:CSV変換処理で想定外の例外が発生するようにする
|
||||
Expects:
|
||||
- CSV変換処理で例外が発生すること
|
||||
- CSV変換処理で発生した例外のログメッセージが出力されていること
|
||||
"""
|
||||
mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3]
|
||||
|
||||
self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {}))
|
||||
self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values)
|
||||
self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME)
|
||||
self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{'Name': 'Test'}], [{'Name': 'Test'}], [{'Name': 'Test'}]])
|
||||
self.mock_backup_crm_data_process = MagicMock()
|
||||
self.mock_convert_crm_csv_data_process = MagicMock(side_effect=[exception(
|
||||
'E-CONV-01', CONV_JP_NAME, '例外発生'), None, None])
|
||||
self.mock_backup_crm_csv_data_process = MagicMock()
|
||||
self.mock_copy_crm_csv_data_process = MagicMock()
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock()
|
||||
self.mock_upload_result_data_process = MagicMock()
|
||||
|
||||
run_control_process()
|
||||
|
||||
assert self.mock_convert_crm_csv_data_process.call_count == 3
|
||||
assert self.mock_backup_crm_csv_data_process.call_count == 2
|
||||
expect_process_result = {
|
||||
'Account': 'fail',
|
||||
'Contact': 'success',
|
||||
'Call2_vod__c': 'success'
|
||||
}
|
||||
assert generate_log_message_tuple(log_message=message) in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(
|
||||
log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'exception, message',
|
||||
[
|
||||
(ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{CSVBK_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'),
|
||||
(ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]')
|
||||
])
|
||||
def test_raise_backup_crm_csv_data_process(self, caplog, exception, message, run_control_process):
|
||||
"""
|
||||
Cases:
|
||||
1. CSVバックアップ処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
2. CSVバックアップ処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
Arranges:
|
||||
- パラメータ1:CSVバックアップ処理でシステム例外が発生するようにする
|
||||
- パラメータ2:CSVバックアップ処理で想定外の例外が発生するようにする
|
||||
Expects:
|
||||
- CSVバックアップ処理で例外が発生すること
|
||||
- CSVバックアップ処理で発生した例外のログメッセージが出力されていること
|
||||
"""
|
||||
mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3]
|
||||
|
||||
self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {}))
|
||||
self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values)
|
||||
self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME)
|
||||
self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{'Name': 'Test'}], [{'Name': 'Test'}], [{'Name': 'Test'}]])
|
||||
self.mock_backup_crm_data_process = MagicMock()
|
||||
self.mock_convert_crm_csv_data_process = MagicMock()
|
||||
self.mock_backup_crm_csv_data_process = MagicMock(side_effect=[exception(
|
||||
'E-CSVBK-01', CSVBK_JP_NAME, '例外発生'), None, None])
|
||||
self.mock_copy_crm_csv_data_process = MagicMock()
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock()
|
||||
self.mock_upload_result_data_process = MagicMock()
|
||||
|
||||
run_control_process()
|
||||
|
||||
assert self.mock_backup_crm_csv_data_process.call_count == 3
|
||||
assert self.mock_copy_crm_csv_data_process.call_count == 2
|
||||
expect_process_result = {
|
||||
'Account': 'fail',
|
||||
'Contact': 'success',
|
||||
'Call2_vod__c': 'success'
|
||||
}
|
||||
assert generate_log_message_tuple(log_message=message) in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(
|
||||
log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'exception, message',
|
||||
[
|
||||
(ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{UPLD_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'),
|
||||
(ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]')
|
||||
])
|
||||
def test_raise_copy_crm_csv_data_process(self, caplog, exception, message, run_control_process):
|
||||
"""
|
||||
Cases:
|
||||
1. CSVアップロード処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
2. CSVアップロード処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
Arranges:
|
||||
- パラメータ1:CSVアップロード処理でシステム例外が発生するようにする
|
||||
- パラメータ2:CSVアップロード処理で想定外の例外が発生するようにする
|
||||
Expects:
|
||||
- CSVアップロード処理で例外が発生すること
|
||||
- CSVアップロード処理で発生した例外のログメッセージが出力されていること
|
||||
"""
|
||||
mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3]
|
||||
|
||||
self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {}))
|
||||
self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values)
|
||||
self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME)
|
||||
self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{'Name': 'Test'}], [{'Name': 'Test'}], [{'Name': 'Test'}]])
|
||||
self.mock_backup_crm_data_process = MagicMock()
|
||||
self.mock_convert_crm_csv_data_process = MagicMock()
|
||||
self.mock_backup_crm_csv_data_process = MagicMock()
|
||||
self.mock_copy_crm_csv_data_process = MagicMock(side_effect=[exception(
|
||||
'E-UPLD-01', UPLD_JP_NAME, '例外発生'), None, None])
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock()
|
||||
self.mock_upload_result_data_process = MagicMock()
|
||||
|
||||
run_control_process()
|
||||
|
||||
assert self.mock_copy_crm_csv_data_process.call_count == 3
|
||||
assert self.mock_upload_last_fetch_datetime_process.call_count == 2
|
||||
expect_process_result = {
|
||||
'Account': 'fail',
|
||||
'Contact': 'success',
|
||||
'Call2_vod__c': 'success'
|
||||
}
|
||||
assert generate_log_message_tuple(log_message=message) in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(
|
||||
log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'exception, message',
|
||||
[
|
||||
(ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{UPD_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'),
|
||||
(ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]')
|
||||
])
|
||||
def test_raise_upload_last_fetch_datetime_process(self, caplog, exception, message, run_control_process):
|
||||
"""
|
||||
Cases:
|
||||
1. 前回取得日時ファイル更新処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
2. 前回取得日時ファイル更新処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること
|
||||
Arranges:
|
||||
- パラメータ1:前回取得日時ファイル更新処理でシステム例外が発生するようにする
|
||||
- パラメータ2:前回取得日時ファイル更新処理で想定外の例外が発生するようにする
|
||||
Expects:
|
||||
- 前回取得日時ファイル更新処理で例外が発生すること
|
||||
- 前回取得日時ファイル更新処理で発生した例外のログメッセージが出力されていること
|
||||
"""
|
||||
mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3]
|
||||
|
||||
self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {}))
|
||||
self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values)
|
||||
self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME)
|
||||
self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{'Name': 'Test'}], [{'Name': 'Test'}], [{'Name': 'Test'}]])
|
||||
self.mock_backup_crm_data_process = MagicMock()
|
||||
self.mock_convert_crm_csv_data_process = MagicMock()
|
||||
self.mock_backup_crm_csv_data_process = MagicMock()
|
||||
self.mock_copy_crm_csv_data_process = MagicMock()
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock(side_effect=[exception(
|
||||
'E-UPD-01', UPD_JP_NAME, '例外発生'), None, None])
|
||||
self.mock_upload_result_data_process = MagicMock()
|
||||
|
||||
run_control_process()
|
||||
|
||||
assert self.mock_upload_last_fetch_datetime_process.call_count == 3
|
||||
expect_process_result = {
|
||||
'Account': 'fail',
|
||||
'Contact': 'success',
|
||||
'Call2_vod__c': 'success'
|
||||
}
|
||||
assert generate_log_message_tuple(log_message=message) in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(
|
||||
log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'exception, message',
|
||||
[
|
||||
(ForTestMeDaCaCRMDataFetchException, f'E-ERR-01 [{END_JP_NAME}]でエラーが発生したため、処理を終了します'),
|
||||
(ForTestException, 'E-ERR-02 予期せぬエラーが発生したため、処理を終了します エラー内容: [例外発生]')
|
||||
])
|
||||
def test_raise_upload_result_data_process(self, caplog, exception, message, run_control_process):
|
||||
"""
|
||||
Cases:
|
||||
1. 取得処理実施結果アップロード処理でシステム例外が発生した場合、エラーで終了すること
|
||||
2. 取得処理実施結果アップロード処理で想定外の例外が発生した場合、エラーで終了すること
|
||||
Arranges:
|
||||
- パラメータ1:取得処理実施結果アップロード処理でシステム例外が発生するようにする
|
||||
- パラメータ2:取得処理実施結果アップロード処理で想定外の例外が発生するようにする
|
||||
Expects:
|
||||
- 取得処理実施結果アップロード処理で例外が発生すること
|
||||
- 取得処理実施結果アップロード処理で発生した例外のログメッセージが出力されていること
|
||||
"""
|
||||
mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3]
|
||||
|
||||
self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {}))
|
||||
self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values)
|
||||
self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME)
|
||||
self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{'Name': 'Test'}], [{'Name': 'Test'}], [{'Name': 'Test'}]])
|
||||
self.mock_backup_crm_data_process = MagicMock()
|
||||
self.mock_convert_crm_csv_data_process = MagicMock()
|
||||
self.mock_backup_crm_csv_data_process = MagicMock()
|
||||
self.mock_copy_crm_csv_data_process = MagicMock()
|
||||
self.mock_upload_last_fetch_datetime_process = MagicMock()
|
||||
self.mock_upload_result_data_process = MagicMock(side_effect=[exception(
|
||||
'E-END-01', END_JP_NAME, '例外発生'), None, None])
|
||||
|
||||
with pytest.raises(exception):
|
||||
run_control_process()
|
||||
|
||||
assert self.mock_upload_result_data_process.called is True
|
||||
assert self.mock_upload_result_data_process.call_count == 1
|
||||
assert generate_log_message_tuple(
|
||||
log_level=logging.ERROR,
|
||||
log_message=message) in caplog.record_tuples, '取得処理実施結果アップロード処理で発生した例外のログメッセージが出力されていること'
|
||||
200
ecs/crm-datafetch/tests/test_convert_crm_csv_data_process.py
Normal file
200
ecs/crm-datafetch/tests/test_convert_crm_csv_data_process.py
Normal file
@ -0,0 +1,200 @@
|
||||
import textwrap
|
||||
from collections import OrderedDict
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from src.config.objects import TargetObject
|
||||
from src.convert_crm_csv_data_process import convert_crm_csv_data_process
|
||||
from src.error.exceptions import DataConvertException
|
||||
from src.system_var.constants import CONV_JP_NAME
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
from .test_utils.log_message import generate_log_message_tuple
|
||||
|
||||
|
||||
class TestConvertCrmCsvDataProcess:
|
||||
|
||||
def test_run_process_success(self, caplog):
|
||||
"""
|
||||
Cases:
|
||||
CSV変換処理が正常終了し、期待通りの結果が返ること
|
||||
Arranges:
|
||||
- チェック対象のdictオブジェクトを宣言する
|
||||
Expects:
|
||||
- CSV変換処理の結果文字列が返却されること
|
||||
- CSV変換処理の仕様に沿った正常系ログが出力されること(デバッグログは除く)
|
||||
"""
|
||||
# Arrange
|
||||
response_json = [
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'Account'),
|
||||
('url', '/services/data/v1.0/sobjects/Account/TEST001')])),
|
||||
('Id', 'TEST001'),
|
||||
('AccountNumber', 'test001'),
|
||||
('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
|
||||
('LastModifiedById', 1.234567E+6),
|
||||
('SystemModstamp', '2022-06-01T00:00:00.000+0000'),
|
||||
('IsDeleted', True),
|
||||
('PersonMailingAddress', OrderedDict([
|
||||
('PersonMailingStreet', 'Lorem ipsum dolor sit amet, \nconsectetur adipiscing elit, \nsed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'), # noqa: E501
|
||||
('PersonMailingCity', 'New york city'),
|
||||
('PersonMailingState', 'Ohaio'),
|
||||
('PersonMailingPostalCode', '999-9999'),
|
||||
('PersonMailingCountry', 'US'),
|
||||
('PersonMailingLatitude', 50.1234567),
|
||||
('PersonMailingLongitude', 103.1234567),
|
||||
('PersonMailingGeocodeAccuracy', 'Address'),
|
||||
])),
|
||||
]),
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'Account'),
|
||||
('url', '/services/data/v1.0/sobjects/Account/TEST002')])),
|
||||
('Id', 'TEST002'),
|
||||
('AccountNumber', 'test002'),
|
||||
('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
|
||||
('LastModifiedById', 1.23E+0),
|
||||
('SystemModstamp', '2022-06-01T00:00:00.000+0000'),
|
||||
('IsDeleted', False),
|
||||
('PersonMailingAddress', OrderedDict([
|
||||
('PersonMailingStreet', 'Lorem ipsum dolor sit amet, \nconsectetur adipiscing elit, \nsed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'), # noqa: E501
|
||||
('PersonMailingCity', 'New york city'),
|
||||
('PersonMailingState', 'Ohaio'),
|
||||
('PersonMailingPostalCode', '999-9999'),
|
||||
('PersonMailingCountry', 'US'),
|
||||
('PersonMailingLatitude', 50.1234567),
|
||||
('PersonMailingLongitude', 103.1234567),
|
||||
('PersonMailingGeocodeAccuracy', 'Address'),
|
||||
])),
|
||||
]),
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'Account'),
|
||||
('url', '/services/data/v1.0/sobjects/Account/TEST003')])),
|
||||
('Id', 'TEST003'),
|
||||
('AccountNumber', 'test003'),
|
||||
('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
|
||||
('LastModifiedById', 1.234567),
|
||||
('SystemModstamp', '2022-06-01T00:00:00.000+0000'),
|
||||
('IsDeleted', False),
|
||||
('PersonMailingAddress', OrderedDict([
|
||||
('PersonMailingStreet', 'Lorem ipsum dolor sit amet, \nconsectetur adipiscing elit, \nsed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'), # noqa: E501
|
||||
('PersonMailingCity', 'New york city'),
|
||||
('PersonMailingState', 'Ohaio'),
|
||||
('PersonMailingPostalCode', '999-9999'),
|
||||
('PersonMailingCountry', 'US'),
|
||||
('PersonMailingLatitude', 50.1234567),
|
||||
('PersonMailingLongitude', 103.1234567),
|
||||
('PersonMailingGeocodeAccuracy', 'Address'),
|
||||
])),
|
||||
]),
|
||||
]
|
||||
|
||||
target_object_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'AccountNumber',
|
||||
'LastModifiedDate',
|
||||
'LastModifiedById',
|
||||
'SystemModstamp',
|
||||
'IsDeleted',
|
||||
'PersonMailingAddress'
|
||||
]
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(target_object_dict, execute_datetime)
|
||||
|
||||
# Act
|
||||
actual_csv_string = convert_crm_csv_data_process(target_object, response_json)
|
||||
|
||||
# Assert
|
||||
|
||||
expect_csv_string = """\
|
||||
"Id","AccountNumber","LastModifiedDate","LastModifiedById","SystemModstamp","IsDeleted","PersonMailingAddress"\r\n\
|
||||
"TEST001","test001","2022-06-01 09:00:00","1234567.0","2022-06-01 09:00:00","1","{""PersonMailingStreet"": ""Lorem ipsum dolor sit amet, \\nconsectetur adipiscing elit, \\nsed do eiusmod tempor incididunt ut labore et dolore magna aliqua."", ""PersonMailingCity"": ""New york city"", ""PersonMailingState"": ""Ohaio"", ""PersonMailingPostalCode"": ""999-9999"", ""PersonMailingCountry"": ""US"", ""PersonMailingLatitude"": 50.1234567, ""PersonMailingLongitude"": 103.1234567, ""PersonMailingGeocodeAccuracy"": ""Address""}"\r\n\
|
||||
"TEST002","test002","2022-06-01 09:00:00","1.23","2022-06-01 09:00:00","0","{""PersonMailingStreet"": ""Lorem ipsum dolor sit amet, \\nconsectetur adipiscing elit, \\nsed do eiusmod tempor incididunt ut labore et dolore magna aliqua."", ""PersonMailingCity"": ""New york city"", ""PersonMailingState"": ""Ohaio"", ""PersonMailingPostalCode"": ""999-9999"", ""PersonMailingCountry"": ""US"", ""PersonMailingLatitude"": 50.1234567, ""PersonMailingLongitude"": 103.1234567, ""PersonMailingGeocodeAccuracy"": ""Address""}"\r\n\
|
||||
"TEST003","test003","2022-06-01 09:00:00","1.234567","2022-06-01 09:00:00","0","{""PersonMailingStreet"": ""Lorem ipsum dolor sit amet, \\nconsectetur adipiscing elit, \\nsed do eiusmod tempor incididunt ut labore et dolore magna aliqua."", ""PersonMailingCity"": ""New york city"", ""PersonMailingState"": ""Ohaio"", ""PersonMailingPostalCode"": ""999-9999"", ""PersonMailingCountry"": ""US"", ""PersonMailingLatitude"": 50.1234567, ""PersonMailingLongitude"": 103.1234567, ""PersonMailingGeocodeAccuracy"": ""Address""}"\r\n\
|
||||
"""
|
||||
# 返り値の期待値チェック
|
||||
assert isinstance(actual_csv_string, str), 'CSV文字列が返却される'
|
||||
assert actual_csv_string == textwrap.dedent(expect_csv_string)
|
||||
|
||||
# ログの確認
|
||||
assert generate_log_message_tuple(log_message='I-CONV-01 [Account] のCSV変換処理を開始します') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message='I-CONV-03 [Account] のCSV変換処理を終了します') in caplog.record_tuples
|
||||
|
||||
def test_call_depended_modules(self):
|
||||
"""
|
||||
Cases:
|
||||
CSV変換処理内で依存しているモジュールが正しく呼ばれていること
|
||||
Arranges:
|
||||
- CSV変換処理の依存モジュールをモック化する
|
||||
Expects:
|
||||
- 依存しているモジュールが正しく呼ばれている
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
target_object_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'AccountNumber',
|
||||
'LastModifiedDate',
|
||||
'LastModifiedById',
|
||||
'SystemModstamp',
|
||||
'IsDeleted'
|
||||
]
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(target_object_dict, execute_datetime)
|
||||
# Act
|
||||
with patch('src.convert_crm_csv_data_process.CSVStringConverter') as mock_converter:
|
||||
inst = mock_converter.return_value
|
||||
inst.convert.return_value = ''
|
||||
convert_crm_csv_data_process(target_object, {})
|
||||
|
||||
# Assert
|
||||
assert mock_converter.called is True
|
||||
assert inst.convert.called is True
|
||||
|
||||
def test_raise_convert(self):
|
||||
"""
|
||||
Cases:
|
||||
CSV変換処理でエラーが発生した場合、検査例外が発生すること
|
||||
Arranges:
|
||||
- CSV変換処理で例外が発生するようにする
|
||||
Expects:
|
||||
- 例外が発生する
|
||||
- 形式チェックが失敗したエラーが返却される
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
target_object_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'AccountNumber',
|
||||
'LastModifiedDate',
|
||||
'LastModifiedById',
|
||||
'SystemModstamp',
|
||||
'IsDeleted'
|
||||
]
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(target_object_dict, execute_datetime)
|
||||
# Act
|
||||
with patch('src.convert_crm_csv_data_process.CSVStringConverter') as mock_converter:
|
||||
inst = mock_converter.return_value
|
||||
inst.convert.side_effect = Exception('変換エラー')
|
||||
with pytest.raises(DataConvertException) as e:
|
||||
convert_crm_csv_data_process(target_object, {})
|
||||
|
||||
# Assert
|
||||
assert mock_converter.called is True
|
||||
assert inst.convert.called is True
|
||||
|
||||
assert e.value.error_id == 'E-CONV-01'
|
||||
assert e.value.func_name == CONV_JP_NAME
|
||||
assert e.value.args[0] == f'[Account] CSV変換に失敗しました エラー内容:[変換エラー]'
|
||||
160
ecs/crm-datafetch/tests/test_copy_crm_csv_data_process.py
Normal file
160
ecs/crm-datafetch/tests/test_copy_crm_csv_data_process.py
Normal file
@ -0,0 +1,160 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from src.config.objects import TargetObject
|
||||
from src.copy_crm_csv_data_process import copy_crm_csv_data_process
|
||||
from src.error.exceptions import FileUploadException
|
||||
from src.system_var.constants import UPLD_JP_NAME
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
from .test_utils.log_message import generate_log_message_tuple
|
||||
|
||||
|
||||
class TestCopyCrmCsvDataProcess:
|
||||
|
||||
@pytest.fixture
|
||||
def bucket_name(self):
|
||||
return 'test-bucket'
|
||||
|
||||
@pytest.fixture
|
||||
def prepare_bucket(self, s3_client, bucket_name):
|
||||
s3_client.create_bucket(Bucket=bucket_name)
|
||||
yield
|
||||
|
||||
def test_run_process_success(self, s3_client, prepare_bucket, bucket_name, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
CSVアップロード処理が正常終了し、期待通りの結果が返ること
|
||||
Arranges:
|
||||
- オブジェクト情報の準備
|
||||
- コピー元のバケットのモックを生成する
|
||||
- コピー先のバケットの環境変数を設定する
|
||||
- コピー元にファイルを配置する
|
||||
Expects:
|
||||
- コピーしたしたCSVファイルが存在し内容が想定と一致する
|
||||
- CSVアップロード処理の仕様に沿った正常系ログが出力されること(デバッグログは除く)
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
|
||||
# オブジェクト情報の準備
|
||||
target_objects_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name'
|
||||
]
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
target_object = TargetObject(target_objects_dict, execute_datetime)
|
||||
|
||||
# コピー元のバケットをモック化
|
||||
from_copy_bucket = 'for-copy-bucket'
|
||||
s3_client.create_bucket(Bucket=from_copy_bucket)
|
||||
monkeypatch.setattr('src.aws.s3.CRM_BACKUP_BUCKET', from_copy_bucket)
|
||||
monkeypatch.setattr('src.copy_crm_csv_data_process.CRM_IMPORT_DATA_BACKUP_FOLDER', 'data_import')
|
||||
|
||||
# コピー先のバケットをモック化
|
||||
monkeypatch.setattr('src.aws.s3.IMPORT_DATA_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.CRM_IMPORT_DATA_FOLDER', 'crm/target')
|
||||
|
||||
# コピー元のモックバケットにデータを配置
|
||||
s3_client.put_object(Bucket=from_copy_bucket,
|
||||
Key=f'data_import/{execute_datetime.to_path()}/CRM_Account_{execute_datetime.format_date()}.csv', Body=b'test,test,test')
|
||||
|
||||
# Act
|
||||
copy_crm_csv_data_process(target_object, execute_datetime)
|
||||
|
||||
# Assert
|
||||
# ファイル確認
|
||||
actual = s3_client.get_object(
|
||||
Bucket=bucket_name, Key=f'crm/target/CRM_Account_{execute_datetime.format_date()}.csv')
|
||||
assert actual['Body'].read().decode('utf-8') == 'test,test,test'
|
||||
|
||||
# ログの確認
|
||||
assert generate_log_message_tuple(
|
||||
log_message=f'I-UPLD-01 [Account] のCSVデータアップロード処理を開始します ファイル名:[CRM_Account_{execute_datetime.format_date()}.csv]') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-UPLD-03 [Account] のCSVデータのアップロード処理を終了します') in caplog.record_tuples
|
||||
|
||||
def test_call_depended_modules(self):
|
||||
"""
|
||||
Cases:
|
||||
CSVアップロード処理内で依存しているモジュールが正しく呼ばれていること
|
||||
Arranges:
|
||||
- CSVアップロード処理の依存モジュールをモック化する
|
||||
- オブジェクト情報の準備
|
||||
Expects:
|
||||
- 依存しているモジュールが正しく呼ばれている
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
|
||||
target_objects_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name'
|
||||
]
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
target_object = TargetObject(target_objects_dict, execute_datetime)
|
||||
|
||||
mock_data_bucket = MagicMock(return_value=None)
|
||||
mock_backup_bucket = MagicMock(return_value=None)
|
||||
mock_copy_bucket = MagicMock(return_value=None)
|
||||
|
||||
# Act
|
||||
with patch('src.aws.s3.DataBucket.__init__', mock_data_bucket), \
|
||||
patch('src.aws.s3.BackupBucket.__init__', mock_backup_bucket), \
|
||||
patch('src.aws.s3.DataBucket.put_csv_from', mock_copy_bucket):
|
||||
copy_crm_csv_data_process(target_object, execute_datetime)
|
||||
|
||||
# Assert
|
||||
assert mock_data_bucket.called is True
|
||||
assert mock_backup_bucket.called is True
|
||||
assert mock_copy_bucket.called is True
|
||||
|
||||
def test_raise_run_process_success(self, s3_client, prepare_bucket, bucket_name, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
CSVデータをコピーできない場合、エラーが発生すること
|
||||
Arranges:
|
||||
- オブジェクト情報の準備
|
||||
- コピー元のバケットのモックを生成する
|
||||
- コピー先のバケットの環境変数を設定する
|
||||
- コピー元にファイルを配置する
|
||||
Expects:
|
||||
- 例外が発生する
|
||||
- ファイルがアップロードできないエラーが返却される
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
|
||||
# オブジェクト情報の準備
|
||||
target_objects_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name'
|
||||
]
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
target_object = TargetObject(target_objects_dict, execute_datetime)
|
||||
|
||||
mock_copy_bucket = MagicMock(side_effect=Exception('ファイルコピーエラー'))
|
||||
|
||||
# Act
|
||||
with patch('src.aws.s3.DataBucket.put_csv_from', mock_copy_bucket):
|
||||
with pytest.raises(FileUploadException) as e:
|
||||
copy_crm_csv_data_process(target_object, execute_datetime)
|
||||
|
||||
# Assert
|
||||
assert e.value.error_id == 'E-UPLD-01'
|
||||
assert e.value.func_name == UPLD_JP_NAME
|
||||
assert e.value.args[0] == f'[Account] CSVデータのアップロードに失敗しました ファイル名:[CRM_Account_{execute_datetime.format_date()}.csv] エラー内容:[ファイルコピーエラー]'
|
||||
438
ecs/crm-datafetch/tests/test_fetch_crm_data_process.py
Normal file
438
ecs/crm-datafetch/tests/test_fetch_crm_data_process.py
Normal file
@ -0,0 +1,438 @@
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from requests.exceptions import ConnectTimeout, ReadTimeout
|
||||
from src.config.objects import LastFetchDatetime, TargetObject
|
||||
from src.error.exceptions import SalesforceAPIException
|
||||
from src.fetch_crm_data_process import fetch_crm_data_process
|
||||
from src.system_var.constants import FETCH_JP_NAME
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
from .test_utils.log_message import generate_log_message_tuple
|
||||
|
||||
common_target_object_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'AccountNumber',
|
||||
'LastModifiedDate',
|
||||
'LastModifiedById',
|
||||
'SystemModstamp',
|
||||
'IsDeleted'
|
||||
]
|
||||
}
|
||||
|
||||
common_last_fetch_datetime_dict = {
|
||||
'last_fetch_datetime_from': '2000-01-01T00:00:00.000Z',
|
||||
'last_fetch_datetime_to': '2010-01-01T00:00:00.000Z',
|
||||
}
|
||||
|
||||
common_execute_datetime = ExecuteDateTime()
|
||||
common_target_object = TargetObject(common_target_object_dict, common_execute_datetime)
|
||||
common_last_fetch_datetime = LastFetchDatetime(common_last_fetch_datetime_dict, common_execute_datetime)
|
||||
|
||||
common_expect = [
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'Account'), ('url', '/services/data/v1.0/sobjects/Account/TEST001')])),
|
||||
('Id', 'TEST001'),
|
||||
('AccountNumber', 'test001'),
|
||||
('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
|
||||
('LastModifiedById', 1.234567E+6),
|
||||
('SystemModstamp', '2022-06-01T00:00:00.000+0000'),
|
||||
('IsDeleted', False)
|
||||
]),
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'Account'), ('url', '/services/data/v1.0/sobjects/Account/TEST002')])),
|
||||
('Id', 'TEST002'),
|
||||
('AccountNumber', 'test002'),
|
||||
('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
|
||||
('LastModifiedById', 1.234567E+6),
|
||||
('SystemModstamp', '2022-06-01T00:00:00.000+0000'),
|
||||
('IsDeleted', False)
|
||||
]),
|
||||
OrderedDict([
|
||||
('attributes', OrderedDict([('type', 'Account'), ('url', '/services/data/v1.0/sobjects/Account/TEST002')])),
|
||||
('Id', 'TEST002'),
|
||||
('AccountNumber', 'test002'),
|
||||
('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
|
||||
('LastModifiedById', 1.234567E+6),
|
||||
('SystemModstamp', '2022-06-01T00:00:00.000+0000'),
|
||||
('IsDeleted', False)
|
||||
]),
|
||||
]
|
||||
|
||||
|
||||
class TestFetchCrmDataProcess:
|
||||
|
||||
def test_run_process_success(self, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
CRMデータ取得処理が正常終了し、期待通りの結果が返ること
|
||||
Arranges:
|
||||
- SalesforceApiClientクラスをモック化し、固定の値を返すようにする
|
||||
Expects:
|
||||
- CRMから取得した取得オブジェクトのリストが返却される
|
||||
- CRMデータ取得処理の仕様に沿った正常系ログが出力されること(デバッグログは除く)
|
||||
"""
|
||||
# Arrange
|
||||
|
||||
# モック化
|
||||
with patch('src.fetch_crm_data_process.SalesforceApiClient') as mock:
|
||||
instance = mock.return_value
|
||||
instance.fetch_sf_count.return_value = len(common_expect)
|
||||
instance.fetch_sf_data.return_value = common_expect
|
||||
# Act
|
||||
actual_crm_data_response = fetch_crm_data_process(common_target_object, common_last_fetch_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
# 返り値の期待値チェック
|
||||
assert isinstance(actual_crm_data_response, list)
|
||||
assert isinstance(actual_crm_data_response[0], OrderedDict)
|
||||
assert actual_crm_data_response == common_expect
|
||||
|
||||
# ログの確認
|
||||
assert generate_log_message_tuple(log_message='I-FETCH-01 [Account] のCRMからのデータ取得処理を開始します') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message='I-FETCH-02 [Account] の件数取得を開始します') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message='I-FETCH-03 [Account] の件数:[3]') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message='I-FETCH-04 [Account] のレコード取得を開始します') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message='I-FETCH-05 [Account] のレコード取得が成功しました') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message='I-FETCH-06 [Account] のCRMからのデータ取得処理を終了します') in caplog.record_tuples
|
||||
|
||||
def test_call_depended_modules(self):
|
||||
"""
|
||||
Cases:
|
||||
CRMデータ取得処理内で依存しているモジュールが正しく呼ばれていること
|
||||
Arranges:
|
||||
- CRMデータ取得処理の依存モジュールをモック化する
|
||||
Expects:
|
||||
- 依存しているモジュールが正しく呼ばれている
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
with patch('src.fetch_crm_data_process.CounterObject', ) as mock_counter, \
|
||||
patch('src.fetch_crm_data_process.SOQLBuilder') as mock_soql_builder, \
|
||||
patch('src.fetch_crm_data_process.SalesforceApiClient') as mock_api_client:
|
||||
# モック化
|
||||
mock_counter_inst = mock_counter.return_value
|
||||
mock_counter_inst.describe.return_value = 1
|
||||
mock_counter_inst.increment.return_value = 1
|
||||
mock_builder_inst = mock_soql_builder.return_value
|
||||
mock_builder_inst.create_count_soql.return_value = ''
|
||||
mock_builder_inst.create_fetch_soql.return_value = ''
|
||||
mock_client_inst = mock_api_client.return_value
|
||||
mock_client_inst.fetch_sf_count.return_value = 1
|
||||
mock_client_inst.fetch_sf_data.return_value = common_expect
|
||||
|
||||
# Act
|
||||
fetch_crm_data_process(common_target_object, common_last_fetch_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_counter.call_count == 2, 'リトライカウント用クラスのインスタンスが生成されていること'
|
||||
assert mock_counter_inst.describe.called is False, 'リトライ用のカウントが取得されていないこと'
|
||||
assert mock_counter_inst.increment.called is False, 'リトライ用のカウントが足されていないこと'
|
||||
assert mock_soql_builder.call_count == 1, 'SOQL生成クラスのインスタンスが生成されていること'
|
||||
assert mock_builder_inst.create_count_soql.called is True, '件数取得SOQLが生成されていること'
|
||||
assert mock_builder_inst.create_fetch_soql.called is True, 'データ取得SOQLが生成されていること'
|
||||
assert mock_api_client.call_count == 2, 'Salesforce APIクライアントのインスタンスが生成されていること'
|
||||
assert mock_client_inst.fetch_sf_count.called is True, '件数の取得が呼ばれていること'
|
||||
assert mock_client_inst.fetch_sf_data.called is True, 'レコードの取得が呼ばれていること'
|
||||
|
||||
def test_raise_create_count_soql(self, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
件数取得用SOQLが生成できない場合、エラーが発生すること
|
||||
Arranges:
|
||||
- 件数取得用SOQL生成処理で例外が発生するようにする
|
||||
Expects:
|
||||
- 例外が発生する
|
||||
- データ件数取得に失敗した旨のエラーが出力される
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
with patch('src.fetch_crm_data_process.SOQLBuilder') as mock_soql_builder, \
|
||||
patch('src.fetch_crm_data_process.SalesforceApiClient') as mock_api_client:
|
||||
# モック化
|
||||
mock_builder_inst = mock_soql_builder.return_value
|
||||
mock_builder_inst.create_count_soql.side_effect = Exception('生成エラー')
|
||||
mock_builder_inst.create_fetch_soql.return_value = ''
|
||||
mock_client_inst = mock_api_client.return_value
|
||||
mock_client_inst.fetch_sf_count.return_value = 1
|
||||
mock_client_inst.fetch_sf_data.return_value = common_expect
|
||||
|
||||
# Act
|
||||
with pytest.raises(SalesforceAPIException) as e:
|
||||
fetch_crm_data_process(common_target_object, common_last_fetch_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
assert e.value.error_id == 'E-FETCH-01'
|
||||
assert e.value.func_name == FETCH_JP_NAME
|
||||
assert e.value.args[0] == f'[Account] の件数取得に失敗しました エラー内容:[生成エラー]'
|
||||
|
||||
def test_raise_create_fetch_soql(self, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
データ取得用SOQLが生成できない場合、エラーが発生すること
|
||||
Arranges:
|
||||
- データ取得用SOQL生成処理で例外が発生するようにする
|
||||
Expects:
|
||||
- 例外が発生する
|
||||
- データ取得に失敗した旨のエラーが出力される
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
with patch('src.fetch_crm_data_process.SOQLBuilder') as mock_soql_builder, \
|
||||
patch('src.fetch_crm_data_process.SalesforceApiClient') as mock_api_client:
|
||||
# モック化
|
||||
mock_builder_inst = mock_soql_builder.return_value
|
||||
mock_builder_inst.create_count_soql.return_value = ''
|
||||
mock_builder_inst.create_fetch_soql.side_effect = Exception('生成エラー')
|
||||
mock_client_inst = mock_api_client.return_value
|
||||
mock_client_inst.fetch_sf_count.return_value = 1
|
||||
mock_client_inst.fetch_sf_data.return_value = common_expect
|
||||
|
||||
# Act
|
||||
with pytest.raises(SalesforceAPIException) as e:
|
||||
fetch_crm_data_process(common_target_object, common_last_fetch_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
assert e.value.error_id == 'E-FETCH-02'
|
||||
assert e.value.func_name == FETCH_JP_NAME
|
||||
assert e.value.args[0] == f'[Account] のレコード取得に失敗しました エラー内容:[生成エラー]'
|
||||
|
||||
@pytest.mark.parametrize('timeout_env_name, exception, expect_message', [
|
||||
('CRM_AUTH_TIMEOUT', ConnectTimeout('接続タイムアウト'), 'W-FETCH-01 CRMの接続処理がタイムアウトしため、リトライします:[1] エラー内容:[接続タイムアウト]'),
|
||||
('CRM_GET_RECORD_COUNT_TIMEOUT', ReadTimeout('読み取りタイムアウト'), 'W-FETCH-02 [Account] の件数取得処理がタイムアウトしたため、リトライします:[1] エラー内容:[読み取りタイムアウト]'),
|
||||
('CRM_AUTH_TIMEOUT', Exception('予期せぬ例外'), 'W-FETCH-03 [Account] の件数取得に失敗したため、リトライします エラー内容:[予期せぬ例外]'),
|
||||
], ids=['connection_timeout', 'read_timeout', 'unexpected_exception'])
|
||||
def test_raise_fetch_sf_count_with_retry_success(self, monkeypatch, caplog, timeout_env_name, exception, expect_message):
|
||||
"""
|
||||
Cases:
|
||||
1. データ件数取得処理で接続タイムアウト例外が発生した場合、リトライした結果復旧し、正常終了すること
|
||||
2. データ件数取得処理で読み取りタイムアウト例外が発生した場合、リトライした結果復旧し、正常終了すること
|
||||
3. データ件数取得処理で予期せぬ例外が発生した場合、リトライした結果復旧し、正常終了すること
|
||||
Arranges:
|
||||
- データ件数取得処理の最大リトライ試行回数を4に設定する
|
||||
- timeout_env_nameに指定されたリトライタイムアウト時間の秒数を1に設定する
|
||||
- データ件数取得処理の初回に接続タイムアウト例外が発生するようにする
|
||||
Expects:
|
||||
- 正常終了する
|
||||
- データ件数取得に失敗した旨のエラーが出力されない
|
||||
"""
|
||||
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT', 4)
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_MAX_INTERVAL', 1)
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_MIN_INTERVAL', 1)
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_INTERVAL', 1)
|
||||
monkeypatch.setattr(f'src.fetch_crm_data_process.{timeout_env_name}', 1)
|
||||
# Arrange
|
||||
with patch('src.fetch_crm_data_process.CounterObject', ) as mock_counter, \
|
||||
patch('src.fetch_crm_data_process.SOQLBuilder') as mock_soql_builder, \
|
||||
patch('src.fetch_crm_data_process.SalesforceApiClient') as mock_api_client:
|
||||
# モック化
|
||||
mock_counter_inst = mock_counter.return_value
|
||||
mock_counter_inst.describe.side_effect = [1, 2, 3, 4]
|
||||
mock_counter_inst.increment.side_effect = [2, 3, 4, 5]
|
||||
mock_builder_inst = mock_soql_builder.return_value
|
||||
mock_builder_inst.create_count_soql.return_value = ''
|
||||
mock_builder_inst.create_fetch_soql.return_value = ''
|
||||
mock_client_inst = mock_api_client.return_value
|
||||
mock_client_inst.fetch_sf_count.side_effect = [exception, len(common_expect)]
|
||||
mock_client_inst.fetch_sf_data.return_value = common_expect
|
||||
# Act
|
||||
fetch_crm_data_process(common_target_object, common_last_fetch_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_counter_inst.describe.call_count == 1
|
||||
assert mock_counter_inst.increment.call_count == 1
|
||||
|
||||
assert generate_log_message_tuple(
|
||||
log_level=logging.WARNING,
|
||||
log_message=expect_message) in caplog.record_tuples
|
||||
called_log_counts = len([log for log in caplog.messages if log == expect_message])
|
||||
assert called_log_counts == 1
|
||||
assert generate_log_message_tuple(log_message='I-FETCH-06 [Account] のCRMからのデータ取得処理を終了します') in caplog.record_tuples
|
||||
|
||||
@pytest.mark.parametrize('timeout_env_name, exception, expect_message', [
|
||||
('CRM_AUTH_TIMEOUT', ConnectTimeout('接続タイムアウト'), 'W-FETCH-01 CRMの接続処理がタイムアウトしため、リトライします:[1] エラー内容:[接続タイムアウト]'),
|
||||
('CRM_GET_RECORD_COUNT_TIMEOUT', ReadTimeout('読み取りタイムアウト'), 'W-FETCH-02 [Account] の件数取得処理がタイムアウトしたため、リトライします:[1] エラー内容:[読み取りタイムアウト]'),
|
||||
('CRM_AUTH_TIMEOUT', Exception('予期せぬ例外'), 'W-FETCH-03 [Account] の件数取得に失敗したため、リトライします エラー内容:[予期せぬ例外]'),
|
||||
], ids=['connection_timeout', 'read_timeout', 'unexpected_exception'])
|
||||
def test_raise_fetch_sf_count_with_retry_fail(self, monkeypatch, caplog, timeout_env_name, exception, expect_message):
|
||||
"""
|
||||
Cases:
|
||||
1. データ件数取得処理で接続タイムアウト例外が発生した場合、リトライした結果復旧せず、異常終了すること
|
||||
2. データ件数取得処理で読み取りタイムアウト例外が発生した場合、リトライした結果復旧せず、異常終了すること
|
||||
3. データ件数取得処理で予期せぬ例外が発生した場合、リトライした結果復旧せず、異常終了すること
|
||||
Arranges:
|
||||
- データ件数取得処理の最大リトライ試行回数を4に設定する
|
||||
- timeout_env_nameに指定されたリトライタイムアウト時間の秒数を1に設定する
|
||||
- データ件数取得処理の1回目、2回目、3回目、4回目で接続タイムアウト例外が発生するようにする
|
||||
Expects:
|
||||
- 異常終了する
|
||||
- データ件数取得に失敗した旨のエラーが出力される
|
||||
"""
|
||||
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT', 4)
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_MAX_INTERVAL', 1)
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_MIN_INTERVAL', 1)
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_INTERVAL', 1)
|
||||
monkeypatch.setattr(f'src.fetch_crm_data_process.{timeout_env_name}', 1)
|
||||
# Arrange
|
||||
with patch('src.fetch_crm_data_process.CounterObject', ) as mock_counter, \
|
||||
patch('src.fetch_crm_data_process.SOQLBuilder') as mock_soql_builder, \
|
||||
patch('src.fetch_crm_data_process.SalesforceApiClient') as mock_api_client:
|
||||
# モック化
|
||||
mock_counter_inst = mock_counter.return_value
|
||||
mock_counter_inst.describe.side_effect = [1, 2, 3, 4]
|
||||
mock_counter_inst.increment.side_effect = [2, 3, 4, 5]
|
||||
mock_builder_inst = mock_soql_builder.return_value
|
||||
mock_builder_inst.create_count_soql.return_value = ''
|
||||
mock_builder_inst.create_fetch_soql.return_value = ''
|
||||
mock_client_inst = mock_api_client.return_value
|
||||
mock_client_inst.fetch_sf_count.side_effect = [exception, exception, exception, exception]
|
||||
mock_client_inst.fetch_sf_data.return_value = common_expect
|
||||
# Act
|
||||
with pytest.raises(SalesforceAPIException) as e:
|
||||
fetch_crm_data_process(common_target_object, common_last_fetch_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
# 取得は4回行われる
|
||||
assert mock_counter_inst.describe.call_count == 4
|
||||
# 足し込みは3回のみ
|
||||
assert mock_counter_inst.increment.call_count == 3
|
||||
|
||||
assert generate_log_message_tuple(
|
||||
log_level=logging.WARNING,
|
||||
log_message=expect_message) in caplog.record_tuples
|
||||
called_log_counts = len([log for log in caplog.messages if log == expect_message])
|
||||
assert called_log_counts == 3
|
||||
assert generate_log_message_tuple(log_message='I-FETCH-06 [Account] のCRMからのデータ取得処理を終了します') not in caplog.record_tuples
|
||||
|
||||
assert e.value.error_id == 'E-FETCH-01'
|
||||
assert e.value.func_name == FETCH_JP_NAME
|
||||
# リトライ例外のオブジェクトIDが違うため、in句で比較
|
||||
assert f'[Account] の件数取得に失敗しました エラー内容:[RetryError' in e.value.args[0]
|
||||
|
||||
@pytest.mark.parametrize('timeout_env_name, exception, expect_message', [
|
||||
('CRM_AUTH_TIMEOUT', ConnectTimeout('接続タイムアウト'), 'W-FETCH-04 CRMの接続処理がタイムアウトしため、リトライします:[1] エラー内容:[接続タイムアウト]'),
|
||||
('CRM_FETCH_RECORD_TIMEOUT', ReadTimeout('読み取りタイムアウト'), 'W-FETCH-05 [Account] のレコード取得処理がタイムアウトしたため、リトライします:[1] エラー内容:[読み取りタイムアウト]'),
|
||||
('CRM_AUTH_TIMEOUT', Exception('予期せぬ例外'), 'W-FETCH-06 [Account] のレコード取得に失敗したため、リトライします エラー内容:[予期せぬ例外]'),
|
||||
], ids=['connection_timeout', 'read_timeout', 'unexpected_exception'])
|
||||
def test_raise_fetch_sf_data_with_retry_success(self, monkeypatch, caplog, timeout_env_name, exception, expect_message):
|
||||
"""
|
||||
Cases:
|
||||
1. レコード取得処理で接続タイムアウト例外が発生した場合、リトライした結果復旧し、正常終了すること
|
||||
2. レコード取得処理で読み取りタイムアウト例外が発生した場合、リトライした結果復旧し、正常終了すること
|
||||
3. レコード取得処理で予期せぬ例外が発生した場合、リトライした結果復旧し、正常終了すること
|
||||
Arranges:
|
||||
- レコード取得処理の最大リトライ試行回数を4に設定する
|
||||
- timeout_env_nameに指定されたリトライタイムアウト時間の秒数を1に設定する
|
||||
- レコード取得処理の初回に接続タイムアウト例外が発生するようにする
|
||||
Expects:
|
||||
- 正常終了する
|
||||
- データレコード取得に失敗した旨のエラーが出力されない
|
||||
"""
|
||||
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT', 4)
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_MAX_INTERVAL', 1)
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_MIN_INTERVAL', 1)
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_INTERVAL', 1)
|
||||
monkeypatch.setattr(f'src.fetch_crm_data_process.{timeout_env_name}', 1)
|
||||
# Arrange
|
||||
with patch('src.fetch_crm_data_process.CounterObject', ) as mock_counter, \
|
||||
patch('src.fetch_crm_data_process.SOQLBuilder') as mock_soql_builder, \
|
||||
patch('src.fetch_crm_data_process.SalesforceApiClient') as mock_api_client:
|
||||
# モック化
|
||||
mock_counter_inst = mock_counter.return_value
|
||||
mock_counter_inst.describe.side_effect = [1, 2, 3, 4]
|
||||
mock_counter_inst.increment.side_effect = [2, 3, 4, 5]
|
||||
mock_builder_inst = mock_soql_builder.return_value
|
||||
mock_builder_inst.create_count_soql.return_value = ''
|
||||
mock_builder_inst.create_fetch_soql.return_value = ''
|
||||
mock_client_inst = mock_api_client.return_value
|
||||
mock_client_inst.fetch_sf_count.return_value = 1
|
||||
mock_client_inst.fetch_sf_data.side_effect = [exception, common_expect]
|
||||
# Act
|
||||
fetch_crm_data_process(common_target_object, common_last_fetch_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_counter_inst.describe.call_count == 1
|
||||
assert mock_counter_inst.increment.call_count == 1
|
||||
|
||||
assert generate_log_message_tuple(
|
||||
log_level=logging.WARNING,
|
||||
log_message=expect_message) in caplog.record_tuples
|
||||
called_log_counts = len([log for log in caplog.messages if log == expect_message])
|
||||
assert called_log_counts == 1
|
||||
assert generate_log_message_tuple(log_message='I-FETCH-06 [Account] のCRMからのデータ取得処理を終了します') in caplog.record_tuples
|
||||
|
||||
@pytest.mark.parametrize('timeout_env_name, exception, expect_message', [
|
||||
('CRM_AUTH_TIMEOUT', ConnectTimeout('接続タイムアウト'), 'W-FETCH-04 CRMの接続処理がタイムアウトしため、リトライします:[1] エラー内容:[接続タイムアウト]'),
|
||||
('CRM_FETCH_RECORD_TIMEOUT', ReadTimeout('読み取りタイムアウト'), 'W-FETCH-05 [Account] のレコード取得処理がタイムアウトしたため、リトライします:[1] エラー内容:[読み取りタイムアウト]'),
|
||||
('CRM_AUTH_TIMEOUT', Exception('予期せぬ例外'), 'W-FETCH-06 [Account] のレコード取得に失敗したため、リトライします エラー内容:[予期せぬ例外]'),
|
||||
], ids=['connection_timeout', 'read_timeout', 'unexpected_exception'])
|
||||
def test_raise_fetch_sf_data_with_retry_fail(self, monkeypatch, caplog, timeout_env_name, exception, expect_message):
|
||||
"""
|
||||
Cases:
|
||||
1. レコード取得処理で接続タイムアウト例外が発生した場合、リトライした結果復旧せず、異常終了すること
|
||||
2. レコード取得処理で読み取りタイムアウト例外が発生した場合、リトライした結果復旧せず、異常終了すること
|
||||
3. レコード取得処理で予期せぬ例外が発生した場合、リトライした結果復旧せず、異常終了すること
|
||||
Arranges:
|
||||
- レコード取得処理の最大リトライ試行回数を4に設定する
|
||||
- timeout_env_nameに指定されたリトライタイムアウト時間の秒数を1に設定する
|
||||
- レコード取得処理の1回目、2回目、3回目、4回目で接続タイムアウト例外が発生するようにする
|
||||
Expects:
|
||||
- 異常終了する
|
||||
- データレコード取得に失敗した旨のエラーが出力される
|
||||
"""
|
||||
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_FETCH_RECORD_MAX_RETRY_ATTEMPT', 4)
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_FETCH_RECORD_RETRY_MAX_INTERVAL', 1)
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_FETCH_RECORD_RETRY_MIN_INTERVAL', 1)
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.CRM_FETCH_RECORD_RETRY_INTERVAL', 1)
|
||||
monkeypatch.setattr(f'src.fetch_crm_data_process.{timeout_env_name}', 1)
|
||||
# Arrange
|
||||
with patch('src.fetch_crm_data_process.CounterObject', ) as mock_counter, \
|
||||
patch('src.fetch_crm_data_process.SOQLBuilder') as mock_soql_builder, \
|
||||
patch('src.fetch_crm_data_process.SalesforceApiClient') as mock_api_client:
|
||||
# モック化
|
||||
mock_counter_inst = mock_counter.return_value
|
||||
mock_counter_inst.describe.side_effect = [1, 2, 3, 4]
|
||||
mock_counter_inst.increment.side_effect = [2, 3, 4, 5]
|
||||
mock_builder_inst = mock_soql_builder.return_value
|
||||
mock_builder_inst.create_count_soql.return_value = ''
|
||||
mock_builder_inst.create_fetch_soql.return_value = ''
|
||||
mock_client_inst = mock_api_client.return_value
|
||||
mock_client_inst.fetch_sf_count.return_value = 1
|
||||
mock_client_inst.fetch_sf_data.side_effect = [exception, exception, exception, exception]
|
||||
# Act
|
||||
with pytest.raises(SalesforceAPIException) as e:
|
||||
fetch_crm_data_process(common_target_object, common_last_fetch_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
# 取得は4回行われる
|
||||
assert mock_counter_inst.describe.call_count == 4
|
||||
# 足し込みは3回のみ
|
||||
assert mock_counter_inst.increment.call_count == 3
|
||||
|
||||
assert generate_log_message_tuple(
|
||||
log_level=logging.WARNING,
|
||||
log_message=expect_message) in caplog.record_tuples
|
||||
called_log_counts = len([log for log in caplog.messages if log == expect_message])
|
||||
assert called_log_counts == 3
|
||||
assert generate_log_message_tuple(log_message='I-FETCH-06 [Account] のCRMからのデータ取得処理を終了します') not in caplog.record_tuples
|
||||
|
||||
assert e.value.error_id == 'E-FETCH-02'
|
||||
assert e.value.func_name == FETCH_JP_NAME
|
||||
# リトライ例外のオブジェクトIDが違うため、in句で比較
|
||||
assert f'[Account] のレコード取得に失敗しました エラー内容:[RetryError' in e.value.args[0]
|
||||
209
ecs/crm-datafetch/tests/test_prepare_data_fetch_process.py
Normal file
209
ecs/crm-datafetch/tests/test_prepare_data_fetch_process.py
Normal file
@ -0,0 +1,209 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from src.config.objects import FetchTargetObjects
|
||||
from src.error.exceptions import FileNotFoundException, InvalidConfigException
|
||||
from src.prepare_data_fetch_process import prepare_data_fetch_process
|
||||
from src.system_var.constants import PRE_JP_NAME, YYYYMMDDTHHMMSSTZ
|
||||
from src.system_var.environments import (CRM_CONFIG_BUCKET,
|
||||
OBJECT_INFO_FILENAME,
|
||||
OBJECT_INFO_FOLDER)
|
||||
|
||||
from .test_utils.log_message import generate_log_message_tuple
|
||||
|
||||
|
||||
class TestPrepareDataFetchProcess:
|
||||
|
||||
@pytest.fixture
|
||||
def bucket_name(self):
|
||||
return 'test-config-bucket'
|
||||
|
||||
@pytest.fixture
|
||||
def prepare_bucket(self, s3_client, bucket_name):
|
||||
s3_client.create_bucket(Bucket=bucket_name)
|
||||
yield
|
||||
|
||||
def test_run_process_success(self, s3_client, prepare_bucket, bucket_name, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
データ取得準備処理が正常終了し、期待通りの結果が返ること
|
||||
Arranges:
|
||||
- prepare_bucketフィクスチャで、CRM_取得オブジェクト情報ファイルを置くためのモックバケットを作る
|
||||
- 作成したモックバケットを指すように環境変数を設定する
|
||||
- CRM_取得オブジェクト情報ファイルを置く
|
||||
Expects:
|
||||
- CRM取得オブジェクトクラスのインスタンスが返却される
|
||||
- CRM取得オブジェクトクラスのインスタンスが期待値と一致
|
||||
- 実行日時クラスのインスタンスが返却される
|
||||
- 実行日時クラスが、テスト実行後の日時よりも小さい
|
||||
- 処理結果出力用の空のdictが返却される
|
||||
- データ取得準備処理の仕様に沿った正常系ログが出力されること(デバッグログは除く)
|
||||
"""
|
||||
# Arrange
|
||||
target_objects_dict = {
|
||||
'objects': [
|
||||
{
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name'
|
||||
]
|
||||
},
|
||||
{
|
||||
'object_name': 'Contact',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name'
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
s3_client.put_object(Bucket=bucket_name, Key='crm/object_info/crm_object_list_diff.json', Body=json.dumps(target_objects_dict))
|
||||
# 環境変数を編集
|
||||
monkeypatch.setattr('src.aws.s3.CRM_CONFIG_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.OBJECT_INFO_FOLDER', 'crm/object_info')
|
||||
monkeypatch.setattr('src.aws.s3.OBJECT_INFO_FILENAME', 'crm_object_list_diff.json')
|
||||
|
||||
# Act
|
||||
actual_fetch_target_objects, actual_execute_datetime, actual_process_result = \
|
||||
prepare_data_fetch_process()
|
||||
|
||||
# Assert
|
||||
|
||||
# 返り値の期待値チェック
|
||||
expect_fetch_target_objects = FetchTargetObjects(target_objects_dict)
|
||||
assert isinstance(actual_fetch_target_objects, FetchTargetObjects), 'CRM取得オブジェクトクラスのインスタンスが返却される'
|
||||
assert all([a == e for a, e in zip(actual_fetch_target_objects, expect_fetch_target_objects)]), 'CRM取得オブジェクトクラスのインスタンスが期待値と一致'
|
||||
assert datetime.strptime(str(actual_execute_datetime), YYYYMMDDTHHMMSSTZ) < datetime.now(), '実行日時がアサーション時点の日時よりも小さいこと'
|
||||
assert actual_process_result == {}, '処理結果出力用の空のdictが返却される'
|
||||
|
||||
# ログの確認
|
||||
assert generate_log_message_tuple(log_message='I-PRE-01 データ取得準備処理を開始します') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-PRE-02 データ取得処理開始日時:{str(actual_execute_datetime)}') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(
|
||||
log_message=f'I-PRE-03 CRM_取得オブジェクト情報ファイルの取得開始します ファイルパス:[s3://{CRM_CONFIG_BUCKET}/{OBJECT_INFO_FOLDER}/{OBJECT_INFO_FILENAME}]') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message='I-PRE-09 データ取得準備処理を終了します') in caplog.record_tuples
|
||||
|
||||
def test_call_depended_modules(self):
|
||||
"""
|
||||
Cases:
|
||||
データ取得準備処理内で依存しているモジュールが正しく呼ばれていること
|
||||
Arranges:
|
||||
- データ取得準備処理の依存モジュールをモック化する
|
||||
Expects:
|
||||
- 依存しているモジュールが正しく呼ばれている
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
mock_config_bucket = MagicMock(return_value='')
|
||||
mock_json_parser = MagicMock(return_value={})
|
||||
mock_fetch_target_objects = MagicMock(return_value=None)
|
||||
# Act
|
||||
with patch('src.aws.s3.ConfigBucket.get_object_info_file', mock_config_bucket), \
|
||||
patch('src.parser.json_parser.JsonParser.parse', mock_json_parser), \
|
||||
patch('src.config.objects.FetchTargetObjects.__init__', mock_fetch_target_objects):
|
||||
prepare_data_fetch_process()
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_config_bucket.called is True
|
||||
assert mock_json_parser.called is True
|
||||
assert mock_fetch_target_objects.called is True
|
||||
|
||||
def test_raise_get_object_info(self, bucket_name, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
CRM_取得オブジェクト情報ファイルを取得できない場合、エラーが発生すること
|
||||
Arranges:
|
||||
- オブジェクト情報ファイル取得処理で例外が発生するようにする
|
||||
Expects:
|
||||
- 例外が発生する
|
||||
- ファイルが読み込めないエラーが返却される
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
monkeypatch.setattr('src.prepare_data_fetch_process.OBJECT_INFO_FILENAME', 'crm_object_list_diff.json')
|
||||
mock_config_bucket = MagicMock(side_effect=Exception('ファイル取得エラー'))
|
||||
mock_json_parser = MagicMock(return_value={})
|
||||
mock_fetch_target_objects = MagicMock(return_value=None)
|
||||
# Act
|
||||
with patch('src.aws.s3.ConfigBucket.get_object_info_file', mock_config_bucket), \
|
||||
patch('src.parser.json_parser.JsonParser.parse', mock_json_parser), \
|
||||
patch('src.config.objects.FetchTargetObjects.__init__', mock_fetch_target_objects):
|
||||
with pytest.raises(FileNotFoundException) as e:
|
||||
prepare_data_fetch_process()
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_config_bucket.called is True
|
||||
assert mock_json_parser.called is False
|
||||
assert mock_fetch_target_objects.called is False
|
||||
assert e.value.error_id == 'E-PRE-01'
|
||||
assert e.value.func_name == PRE_JP_NAME
|
||||
assert e.value.args[0] == f'CRM_取得オブジェクト情報ファイルが存在しません ファイル名:[crm_object_list_diff.json] エラー内容:[ファイル取得エラー]'
|
||||
|
||||
def test_raise_parse_json_str(self, bucket_name, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
CRM_取得オブジェクト情報ファイルをパースできない場合、エラーが発生すること
|
||||
Arranges:
|
||||
- オブジェクト情報ファイルパース処理で例外が発生するようにする
|
||||
Expects:
|
||||
- 例外が発生する
|
||||
- パースが失敗した旨、エラーメッセージが表示される
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
mock_config_bucket = MagicMock(return_value='')
|
||||
mock_json_parser = MagicMock(side_effect=Exception('JSONパースエラー'))
|
||||
mock_fetch_target_objects = MagicMock(return_value=None)
|
||||
# Act
|
||||
with patch('src.aws.s3.ConfigBucket.get_object_info_file', mock_config_bucket), \
|
||||
patch('src.parser.json_parser.JsonParser.parse', mock_json_parser), \
|
||||
patch('src.config.objects.FetchTargetObjects.__init__', mock_fetch_target_objects):
|
||||
with pytest.raises(InvalidConfigException) as e:
|
||||
prepare_data_fetch_process()
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_config_bucket.called is True
|
||||
assert mock_json_parser.called is True
|
||||
assert mock_fetch_target_objects.called is False
|
||||
assert e.value.error_id == 'E-PRE-02'
|
||||
assert e.value.func_name == PRE_JP_NAME
|
||||
assert e.value.args[0] == f'CRM_取得オブジェクト情報ファイルのパースに失敗しました エラー内容:[JSONパースエラー]'
|
||||
|
||||
def test_raise_check_objects_format(self, bucket_name, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
CRM_取得オブジェクト情報のキーobjectsの形式チェックが不正の場合、エラーが発生すること
|
||||
Arranges:
|
||||
- CRM_取得オブジェクト情報のキーobjectsの形式チェックで例外が発生するようにする
|
||||
Expects:
|
||||
- 例外が発生する
|
||||
- 形式チェックが失敗した旨、エラーメッセージが表示される
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
monkeypatch.setattr('src.prepare_data_fetch_process.OBJECT_INFO_FILENAME', 'crm_object_list_diff.json')
|
||||
mock_config_bucket = MagicMock(return_value='')
|
||||
mock_json_parser = MagicMock(return_value={})
|
||||
mock_fetch_target_objects = MagicMock(side_effect=Exception('形式チェックエラー'))
|
||||
# Act
|
||||
with patch('src.aws.s3.ConfigBucket.get_object_info_file', mock_config_bucket), \
|
||||
patch('src.parser.json_parser.JsonParser.parse', mock_json_parser), \
|
||||
patch('src.config.objects.FetchTargetObjects.__init__', mock_fetch_target_objects):
|
||||
with pytest.raises(InvalidConfigException) as e:
|
||||
prepare_data_fetch_process()
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_config_bucket.called is True
|
||||
assert mock_json_parser.called is True
|
||||
assert mock_fetch_target_objects.called is True
|
||||
assert e.value.error_id == 'E-PRE-03'
|
||||
assert e.value.func_name == PRE_JP_NAME
|
||||
assert e.value.args[0] == 'CRM_取得オブジェクト情報ファイルの形式チェックに失敗しました ファイル名:[crm_object_list_diff.json] エラー内容:[形式チェックエラー]'
|
||||
238
ecs/crm-datafetch/tests/test_set_datetime_period_process.py
Normal file
238
ecs/crm-datafetch/tests/test_set_datetime_period_process.py
Normal file
@ -0,0 +1,238 @@
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from src.config.objects import LastFetchDatetime, TargetObject
|
||||
from src.error.exceptions import FileNotFoundException, InvalidConfigException
|
||||
from src.set_datetime_period_process import set_datetime_period_process
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
from .test_utils.log_message import generate_log_message_tuple
|
||||
|
||||
|
||||
class TestSetDatetimePeriodProcess:
|
||||
|
||||
@pytest.fixture
|
||||
def bucket_name(self):
|
||||
return 'test-config-bucket'
|
||||
|
||||
@pytest.fixture
|
||||
def prepare_bucket(self, s3_client, bucket_name):
|
||||
s3_client.create_bucket(Bucket=bucket_name)
|
||||
yield
|
||||
|
||||
def test_run_process_success(self, s3_client, prepare_bucket, bucket_name, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
データ取得期間設定処理が正常終了し、期待通りの結果が返ること
|
||||
Arranges:
|
||||
- チェック対象のdictオブジェクトを宣言する
|
||||
Expects:
|
||||
- チェック後のオブジェクト情報コレクションクラスのインスタンスが返却される
|
||||
- データ取得期間設定処理の仕様に沿った正常系ログが出力されること(デバッグログは除く)
|
||||
"""
|
||||
# Arrange
|
||||
target_objects_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name'
|
||||
]
|
||||
}
|
||||
|
||||
last_fetch_datetime_dict = {
|
||||
'last_fetch_datetime_from': '1999-01-01T00:00:00.000Z',
|
||||
'last_fetch_datetime_to': '2100-12-31T23:59:59.000Z',
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(target_objects_dict, execute_datetime)
|
||||
s3_client.put_object(Bucket=bucket_name, Key='crm/last_fetch_datetime/Account.json', Body=json.dumps(last_fetch_datetime_dict))
|
||||
# 環境変数を編集
|
||||
monkeypatch.setattr('src.aws.s3.CRM_CONFIG_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.set_datetime_period_process.CRM_CONFIG_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.LAST_FETCH_DATE_FOLDER', 'crm/last_fetch_datetime')
|
||||
# Act
|
||||
actual_last_fetch_datetime = set_datetime_period_process(target_object, execute_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
# 返り値の期待値チェック
|
||||
assert isinstance(actual_last_fetch_datetime, LastFetchDatetime), '最終取得日時オブジェクトクラスのインスタンスが返却される'
|
||||
assert actual_last_fetch_datetime.last_fetch_datetime_from == '1999-01-01T00:00:00.000Z'
|
||||
assert actual_last_fetch_datetime.last_fetch_datetime_to == '2100-12-31T23:59:59.000Z'
|
||||
|
||||
# ログの確認
|
||||
assert generate_log_message_tuple(log_message='I-DATE-01 [Account] のデータ取得期間設定処理を開始します') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(
|
||||
log_message='I-DATE-02 前回取得日時ファイルの取得開始します ファイルパス:[s3://test-config-bucket/crm/last_fetch_datetime/Account.json]') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message='I-DATE-03 前回取得日時ファイルの取得成功しました') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(
|
||||
log_message='I-DATE-06 取得範囲 From: [1999-01-01T00:00:00.000Z] To: [2100-12-31T23:59:59.000Z]') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message='I-DATE-07 [Account] のデータ取得期間設定処理を終了します') in caplog.record_tuples
|
||||
|
||||
def test_call_depended_modules(self, s3_client, prepare_bucket, bucket_name, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
データ取得期間設定処理内で依存しているモジュールが正しく呼ばれていること
|
||||
Arranges:
|
||||
- データ取得期間設定処理内の依存モジュールをモック化する
|
||||
Expects:
|
||||
- 依存しているモジュールが正しく呼ばれている
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
target_objects_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name'
|
||||
]
|
||||
}
|
||||
|
||||
last_fetch_datetime_dict = {
|
||||
'last_fetch_datetime_from': '1999-01-01T00:00:00.000Z',
|
||||
'last_fetch_datetime_to': '2100-12-31T23:59:59.000Z',
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(target_objects_dict, execute_datetime)
|
||||
mock_config_bucket = MagicMock(return_value=last_fetch_datetime_dict)
|
||||
mock_json_parser = MagicMock(return_value=last_fetch_datetime_dict)
|
||||
# Act
|
||||
with patch('src.aws.s3.ConfigBucket.get_last_fetch_datetime_file', mock_config_bucket), \
|
||||
patch('src.parser.json_parser.JsonParser.parse', mock_json_parser):
|
||||
set_datetime_period_process(target_object, execute_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_config_bucket.called is True
|
||||
assert mock_json_parser.called is True
|
||||
|
||||
def test_raise_get_last_fetch_datetime_file(self):
|
||||
"""
|
||||
Cases:
|
||||
最終取得日時ファイル取得処理でエラーが発生すること
|
||||
Arranges:
|
||||
- オブジェクト形式チェック処理で例外が発生するようにする
|
||||
Expects:
|
||||
- 例外が発生する
|
||||
- 形式チェックが失敗したエラーが返却される
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
target_objects_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name'
|
||||
]
|
||||
}
|
||||
|
||||
last_fetch_datetime_dict = {
|
||||
'last_fetch_datetime_from': '1999-01-01T00:00:00.000Z',
|
||||
'last_fetch_datetime_to': '2100-12-31T23:59:59.000Z',
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(target_objects_dict, execute_datetime)
|
||||
mock_config_bucket = MagicMock(side_effect=Exception('取得エラー'))
|
||||
mock_json_parser = MagicMock(return_value=last_fetch_datetime_dict)
|
||||
mock_last_fetch_datetime = MagicMock(return_value=None)
|
||||
|
||||
# Act
|
||||
with patch('src.aws.s3.ConfigBucket.get_last_fetch_datetime_file', mock_config_bucket), \
|
||||
patch('src.parser.json_parser.JsonParser.parse', mock_json_parser), \
|
||||
patch('src.config.objects.LastFetchDatetime.__init__', mock_last_fetch_datetime):
|
||||
with pytest.raises(FileNotFoundException) as e:
|
||||
set_datetime_period_process(target_object, execute_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_config_bucket.called is True
|
||||
assert mock_json_parser.called is False
|
||||
assert e.value.args[0] == f'前回取得日時ファイルが存在しません ファイル名:[Account.json] エラー内容:[取得エラー]'
|
||||
|
||||
def test_raise_json_parse(self):
|
||||
"""
|
||||
Cases:
|
||||
Jsonパース処理でエラーが発生すること
|
||||
Arranges:
|
||||
- Jsonパース処理で例外が発生するようにする
|
||||
Expects:
|
||||
- 例外が発生する
|
||||
- 形式チェックが失敗したエラーが返却される
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
target_objects_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name'
|
||||
]
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(target_objects_dict, execute_datetime)
|
||||
mock_config_bucket = MagicMock(return_value='')
|
||||
mock_json_parser = MagicMock(side_effect=Exception('変換エラー'))
|
||||
mock_last_fetch_datetime = MagicMock(return_value=None)
|
||||
|
||||
# Act
|
||||
with patch('src.aws.s3.ConfigBucket.get_last_fetch_datetime_file', mock_config_bucket), \
|
||||
patch('src.parser.json_parser.JsonParser.parse', mock_json_parser), \
|
||||
patch('src.config.objects.LastFetchDatetime.__init__', mock_last_fetch_datetime):
|
||||
with pytest.raises(InvalidConfigException) as e:
|
||||
set_datetime_period_process(target_object, execute_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_config_bucket.called is True
|
||||
assert mock_json_parser.called is True
|
||||
assert e.value.args[0] == f'前回取得日時ファイルの形式チェック処理が失敗しました エラー内容:[変換エラー]'
|
||||
|
||||
def test_raise_check_last_fetch_datetime(self):
|
||||
"""
|
||||
Cases:
|
||||
Jsonパース処理でエラーが発生すること
|
||||
Arranges:
|
||||
- Jsonパース処理で例外が発生するようにする
|
||||
Expects:
|
||||
- 例外が発生する
|
||||
- 形式チェックが失敗したエラーが返却される
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
target_objects_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name'
|
||||
]
|
||||
}
|
||||
|
||||
last_fetch_datetime_dict = {
|
||||
'last_fetch_datetime_from': '1999-01-01T00:00:00.000Z',
|
||||
'last_fetch_datetime_to': '2100-12-31T23:59:59.000Z',
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
target_object = TargetObject(target_objects_dict, execute_datetime)
|
||||
mock_config_bucket = MagicMock(return_value='')
|
||||
mock_json_parser = MagicMock(return_value=last_fetch_datetime_dict)
|
||||
mock_last_fetch_datetime = MagicMock(side_effect=Exception('形式エラー'))
|
||||
|
||||
# Act
|
||||
with patch('src.aws.s3.ConfigBucket.get_last_fetch_datetime_file', mock_config_bucket), \
|
||||
patch('src.parser.json_parser.JsonParser.parse', mock_json_parser), \
|
||||
patch('src.config.objects.LastFetchDatetime.__init__', mock_last_fetch_datetime):
|
||||
with pytest.raises(InvalidConfigException) as e:
|
||||
set_datetime_period_process(target_object, execute_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_config_bucket.called is True
|
||||
assert mock_json_parser.called is True
|
||||
assert mock_last_fetch_datetime.called is True
|
||||
assert e.value.args[0] == f'前回取得日時ファイルの形式チェック処理が失敗しました エラー内容:[形式エラー]'
|
||||
@ -0,0 +1,218 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from src.config.objects import LastFetchDatetime, TargetObject
|
||||
from src.error.exceptions import FileUploadException
|
||||
from src.system_var.constants import UPD_JP_NAME
|
||||
from src.upload_last_fetch_datetime_process import \
|
||||
upload_last_fetch_datetime_process
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
from .test_utils.log_message import generate_log_message_tuple
|
||||
|
||||
|
||||
class TestUploadLastFetchDatetimeProcess:
|
||||
|
||||
@pytest.fixture
|
||||
def bucket_name(self):
|
||||
return 'test-config-bucket'
|
||||
|
||||
@pytest.fixture
|
||||
def prepare_bucket(self, s3_client, bucket_name):
|
||||
s3_client.create_bucket(Bucket=bucket_name)
|
||||
yield
|
||||
|
||||
def test_run_process_success(self, s3_client, prepare_bucket, bucket_name, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
前回取得日時ファイル更新処理が正常終了し、期待通りの結果が返ること
|
||||
Arranges:
|
||||
- prepare_bucketフィクスチャで、前回取得日時ファイルを置くためのモックバケットを作る
|
||||
- モックバケットを指すように環境変数を設定する
|
||||
- オブジェクト情報を準備する
|
||||
- 最終更新日時インスタンスを準備する
|
||||
Expects:
|
||||
- PUTした前回取得日時ファイルが存在し内容が想定と一致する
|
||||
- 前回取得日時ファイル更新処理の仕様に沿った正常系ログが出力されること(デバッグログは除く)
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
|
||||
# 環境変数を編集
|
||||
monkeypatch.setattr('src.aws.s3.CRM_CONFIG_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.LAST_FETCH_DATE_FOLDER', 'crm/last_fetch_datetime')
|
||||
|
||||
target_objects_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name'
|
||||
],
|
||||
'is_update_last_fetch_datetime': True
|
||||
}
|
||||
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": "1999-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": "2100-12-31T23:59:59.000Z",
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
target_object = TargetObject(target_objects_dict, execute_datetime)
|
||||
last_fetch_datetime = LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Act
|
||||
upload_last_fetch_datetime_process(target_object, last_fetch_datetime)
|
||||
|
||||
# Assert
|
||||
# ファイル確認
|
||||
actual = s3_client.get_object(Bucket=bucket_name, Key=f'crm/last_fetch_datetime/Account.json')
|
||||
assert actual['Body'].read().decode(
|
||||
'utf-8') == '{"last_fetch_datetime_from": "2100-12-31T23:59:59.000Z", "last_fetch_datetime_to": ""}'
|
||||
|
||||
# ログの確認
|
||||
assert generate_log_message_tuple(log_message='I-UPD-01 [Account] の前回取得日時ファイルの更新処理を開始します') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message='I-UPD-02 [Account] の前回取得日時ファイルの更新処理をスキップします') not in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-UPD-04 [Account] の前回取得日時ファイルの更新処理を終了します') in caplog.record_tuples
|
||||
|
||||
def test_run_process_success_skip(self, s3_client, prepare_bucket, bucket_name, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
前回取得日時ファイル更新処理が正常終了し、期待通りの結果が返ること
|
||||
Arranges:
|
||||
- prepare_bucketフィクスチャで、前回取得日時ファイルを置くためのモックバケットを作る
|
||||
- モックバケットを指すように環境変数を設定する
|
||||
- ファイルPUT処理のモックを準備する
|
||||
- オブジェクト情報を準備する
|
||||
- 最終更新日時インスタンスを準備する
|
||||
Expects:
|
||||
- ファイルPUT処理が実行されないこと
|
||||
- 前回取得日時ファイル更新処理の仕様に沿った正常系ログが出力されること(デバッグログは除く)
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
|
||||
# 環境変数を編集
|
||||
monkeypatch.setattr('src.aws.s3.CRM_CONFIG_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.LAST_FETCH_DATE_FOLDER', 'crm/last_fetch_datetime')
|
||||
|
||||
mock_config_bucket = MagicMock(return_value=None)
|
||||
|
||||
target_objects_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name'
|
||||
],
|
||||
'is_update_last_fetch_datetime': False
|
||||
}
|
||||
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": "1999-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": "2100-12-31T23:59:59.000Z",
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
target_object = TargetObject(target_objects_dict, execute_datetime)
|
||||
last_fetch_datetime = LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Act
|
||||
with patch('src.aws.s3.ConfigBucket.put_last_fetch_datetime_file', mock_config_bucket):
|
||||
upload_last_fetch_datetime_process(target_object, last_fetch_datetime)
|
||||
|
||||
# Assert
|
||||
# 処理実行確認
|
||||
assert mock_config_bucket.called is False
|
||||
|
||||
# ログの確認
|
||||
assert generate_log_message_tuple(log_message='I-UPD-01 [Account] の前回取得日時ファイルの更新処理を開始します') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message='I-UPD-02 [Account] の前回取得日時ファイルの更新処理をスキップします') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-UPD-04 [Account] の前回取得日時ファイルの更新処理を終了します') not in caplog.record_tuples
|
||||
|
||||
def test_call_depended_modules(self):
|
||||
"""
|
||||
Cases:
|
||||
前回取得日時ファイル更新処理内で依存しているモジュールが正しく呼ばれていること
|
||||
Arranges:
|
||||
- 前回取得日時ファイル更新処理の依存モジュールをモック化する
|
||||
- オブジェクト情報を準備する
|
||||
- 最終更新日時インスタンスを準備する
|
||||
Expects:
|
||||
- 依存しているモジュールが正しく呼ばれている
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
mock_config_bucket = MagicMock(return_value=None)
|
||||
|
||||
target_objects_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name'
|
||||
],
|
||||
'is_update_last_fetch_datetime': True
|
||||
}
|
||||
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": "1999-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": "2100-12-31T23:59:59.000Z",
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
target_object = TargetObject(target_objects_dict, execute_datetime)
|
||||
last_fetch_datetime = LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Act
|
||||
with patch('src.aws.s3.ConfigBucket.put_last_fetch_datetime_file', mock_config_bucket):
|
||||
upload_last_fetch_datetime_process(target_object, last_fetch_datetime)
|
||||
|
||||
# Assert
|
||||
assert mock_config_bucket.called is True
|
||||
|
||||
def test_raise_put_last_fetch_datetime_file(self, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
前回取得日時ファイルをアップロードできない場合、エラーが発生すること
|
||||
Arranges:
|
||||
- 前回取得日時ファイル更新処理で例外を発生させるモックを生成する
|
||||
- 取得処理実施結果を準備する
|
||||
- 実行日時取得インスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生する
|
||||
- ファイルがアップロードできないエラーが返却される
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
mock_config_bucket = MagicMock(side_effect=Exception('ファイルアップロードエラー'))
|
||||
|
||||
target_objects_dict = {
|
||||
'object_name': 'Account',
|
||||
'columns': [
|
||||
'Id',
|
||||
'Name'
|
||||
],
|
||||
'is_update_last_fetch_datetime': True
|
||||
}
|
||||
|
||||
last_fetch_datetime_dict = {
|
||||
"last_fetch_datetime_from": "1999-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": "2100-12-31T23:59:59.000Z",
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
target_object = TargetObject(target_objects_dict, execute_datetime)
|
||||
last_fetch_datetime = LastFetchDatetime(last_fetch_datetime_dict, execute_datetime)
|
||||
|
||||
# Act
|
||||
with patch('src.aws.s3.ConfigBucket.put_last_fetch_datetime_file', mock_config_bucket):
|
||||
with pytest.raises(FileUploadException) as e:
|
||||
upload_last_fetch_datetime_process(target_object, last_fetch_datetime)
|
||||
|
||||
# Assert
|
||||
assert mock_config_bucket.called is True
|
||||
assert e.value.error_id == 'E-UPD-01'
|
||||
assert e.value.func_name == UPD_JP_NAME
|
||||
assert e.value.args[0] == f'[Account] 前回処理日時ファイルのアップロードに失敗しました ファイル名:[Account.json] エラー内容:[ファイルアップロードエラー]'
|
||||
124
ecs/crm-datafetch/tests/test_upload_result_data_process.py
Normal file
124
ecs/crm-datafetch/tests/test_upload_result_data_process.py
Normal file
@ -0,0 +1,124 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from src.error.exceptions import FileUploadException
|
||||
from src.system_var.constants import END_JP_NAME
|
||||
from src.upload_result_data_process import upload_result_data_process
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
from .test_utils.log_message import generate_log_message_tuple
|
||||
|
||||
|
||||
class TestUploadResultDataProcess:
|
||||
|
||||
@pytest.fixture
|
||||
def bucket_name(self):
|
||||
return 'test-config-bucket'
|
||||
|
||||
@pytest.fixture
|
||||
def prepare_bucket(self, s3_client, bucket_name):
|
||||
s3_client.create_bucket(Bucket=bucket_name)
|
||||
yield
|
||||
|
||||
def test_run_process_success(self, s3_client, prepare_bucket, bucket_name, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
取得処理実施結果アップロード処理が正常終了し、期待通りの結果が返ること
|
||||
Arranges:
|
||||
- prepare_bucketフィクスチャで、取得処理実施結果ファイルを置くためのモックバケットを作る
|
||||
- モックバケットを指すように環境変数を設定する
|
||||
- 取得処理実施結果を準備する
|
||||
- 実行日時取得インスタンスを生成する
|
||||
Expects:
|
||||
- PUTした取得処理実施結果ファイルが存在し内容が想定と一致する
|
||||
- 取得処理実施結果アップロード処理の仕様に沿った正常系ログが出力されること(デバッグログは除く)
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
|
||||
# 環境変数を編集
|
||||
monkeypatch.setattr('src.aws.s3.CRM_BACKUP_BUCKET', bucket_name)
|
||||
monkeypatch.setattr('src.aws.s3.PROCESS_RESULT_FOLDER', 'data_import')
|
||||
|
||||
process_result = {
|
||||
"Account": "success",
|
||||
"Contact": "fail"
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
# Act
|
||||
upload_result_data_process(process_result, execute_datetime)
|
||||
|
||||
# Assert
|
||||
# ファイル確認
|
||||
actual = s3_client.get_object(Bucket=bucket_name, Key=f'data_import/{execute_datetime.to_path()}/process_result.json')
|
||||
assert actual['Body'].read().decode('utf-8') == '{"Account": "success", "Contact": "fail"}'
|
||||
|
||||
# ログの確認
|
||||
assert generate_log_message_tuple(log_message='I-END-01 取得処理実施結果アップロード処理を開始します') in caplog.record_tuples
|
||||
assert generate_log_message_tuple(log_message=f'I-END-03 取得処理実施結果アップロード処理を終了します') in caplog.record_tuples
|
||||
|
||||
def test_call_depended_modules(self):
|
||||
"""
|
||||
Cases:
|
||||
取得処理実施結果アップロード処理内で依存しているモジュールが正しく呼ばれていること
|
||||
Arranges:
|
||||
- 取得処理実施結果アップロード処理の依存モジュールをモック化する
|
||||
- 取得処理実施結果を準備する
|
||||
- 実行日時取得インスタンスを生成する
|
||||
Expects:
|
||||
- 依存しているモジュールが正しく呼ばれている
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
mock_backup_bucket = MagicMock(return_value=None)
|
||||
|
||||
process_result = {
|
||||
"Account": "success",
|
||||
"Contact": "fail"
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
# Act
|
||||
with patch('src.aws.s3.BackupBucket.put_result_json', mock_backup_bucket):
|
||||
upload_result_data_process(process_result, execute_datetime)
|
||||
|
||||
# Assert
|
||||
assert mock_backup_bucket.called is True
|
||||
|
||||
def test_raise_put_result_json(self, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
取得処理実施結果をアップロードできない場合、エラーが発生すること
|
||||
Arranges:
|
||||
- 取得処理実施結果アップロード処理で例外を発生させるモックを生成する
|
||||
- 取得処理実施結果を準備する
|
||||
- 実行日時取得インスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生する
|
||||
- ファイルがアップロードできないエラーが返却される
|
||||
"""
|
||||
|
||||
# Arrange
|
||||
mock_backup_bucket = MagicMock(side_effect=Exception('ファイルアップロードエラー'))
|
||||
|
||||
process_result = {
|
||||
"Account": "success",
|
||||
"Contact": "fail"
|
||||
}
|
||||
|
||||
execute_datetime = ExecuteDateTime()
|
||||
|
||||
# Act
|
||||
with patch('src.aws.s3.BackupBucket.put_result_json', mock_backup_bucket):
|
||||
with pytest.raises(FileUploadException) as e:
|
||||
upload_result_data_process(process_result, execute_datetime)
|
||||
|
||||
# Assert
|
||||
|
||||
assert mock_backup_bucket.called is True
|
||||
assert e.value.error_id == 'E-END-01'
|
||||
assert e.value.func_name == END_JP_NAME
|
||||
assert e.value.args[0] == f'取得処理実施結果のアップロードに失敗しました ファイル名:[process_result.json] エラー内容:[ファイルアップロードエラー]'
|
||||
7
ecs/crm-datafetch/tests/test_utils/log_message.py
Normal file
7
ecs/crm-datafetch/tests/test_utils/log_message.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""ログメッセージに関連するテストヘルパー"""
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
def generate_log_message_tuple(logger_name='root', log_level=logging.INFO, log_message=''):
|
||||
return (logger_name, log_level, log_message)
|
||||
312
ecs/crm-datafetch/tests/test_walk_through.py
Normal file
312
ecs/crm-datafetch/tests/test_walk_through.py
Normal file
@ -0,0 +1,312 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import os.path as path
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import boto3
|
||||
import pytest
|
||||
from src.controller import controller
|
||||
from src.parser.json_parser import JsonParser
|
||||
from src.system_var.constants import YYYYMMDDTHHMMSSTZ
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
ROOT_DIR = path.abspath(path.dirname(__name__))
|
||||
|
||||
|
||||
# ↓↓↓モックテスト用(バケットをモック化する際、コメントアウトを外す。AWS利用テスト用はコメントアウトする)↓↓↓
|
||||
# DATA_BUCKET = 'mbj-newdwh2021-staging-data'
|
||||
# CONFIG_BUCKET = 'mbj-newdwh2021-staging-config'
|
||||
# BACKUP_BUCKET = 'mbj-newdwh2021-staging-backup-crm'
|
||||
#
|
||||
# @pytest.fixture
|
||||
# def s3_test(s3_client):
|
||||
# s3_client.create_bucket(Bucket=DATA_BUCKET)
|
||||
# s3_client.create_bucket(Bucket=CONFIG_BUCKET)
|
||||
# s3_client.create_bucket(Bucket=BACKUP_BUCKET)
|
||||
# yield
|
||||
# ↑↑↑モックテスト用↑↑↑
|
||||
|
||||
# ↓↓↓AWS利用テスト用(AWS上のバケットを利用する際、コメントアウトを外す。モックテスト用はコメントアウトする)↓↓↓
|
||||
DATA_BUCKET = 'test-mbj-newdwh2021-test-data'
|
||||
CONFIG_BUCKET = 'test-mbj-newdwh2021-test-config'
|
||||
BACKUP_BUCKET = 'test-mbj-newdwh2021-test-backup-crm'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def s3_test(s3_client):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def s3_client():
|
||||
s3_client = boto3.client("s3")
|
||||
yield s3_client
|
||||
# ↑↑↑AWS利用テスト用↑↑↑
|
||||
|
||||
|
||||
TARGET_FOLDER = 'crm/target'
|
||||
OBJECT_INFO_FOLDER = 'crm/object_info'
|
||||
LAST_FETCH_DATETIME_INFO_FOLDER = 'crm/last_fetch_datetime'
|
||||
BACKUP_DATA_IMPORT_FOLDER = 'data_import'
|
||||
BACKUP_RESPONSE_JSON_FOLDER = 'response_json'
|
||||
PROCESS_RESULT_JSON_FILE_NAME = 'process_result.json'
|
||||
|
||||
|
||||
@pytest.mark.walk_through
|
||||
def test_walk_through(s3_test, s3_client, monkeypatch, caplog):
|
||||
"""
|
||||
Cases:
|
||||
コントロール処理の頭から最後まで処理が流れきることを確認する
|
||||
Arranges:
|
||||
- 実行に必要なモックバケットを用意する
|
||||
- 実行に必要なファイルをアップロードする
|
||||
- 実行に必要な環境変数をmonkeypatchで設定する
|
||||
- CRMデータカウント取得処理をモック化する(ログ出力にのみ使用しているため)
|
||||
Expects:
|
||||
- すべてのログが仕様どおり二出力されていること(デバッグログを除く)
|
||||
- すべてのファイルが使用どおりの場所にアップロードされていること
|
||||
"""
|
||||
# Arrange
|
||||
# バケットにファイルをアップロードしていく
|
||||
object_info_files = []
|
||||
object_info_list = get_object_config_list('object_info')
|
||||
for object_info in object_info_list:
|
||||
json_file = read_json(object_info)
|
||||
upload_json(json_file, s3_client, CONFIG_BUCKET, f'{OBJECT_INFO_FOLDER}/{path.basename(object_info)}')
|
||||
object_info_files.append(json_file)
|
||||
last_fetch_datetime_list = get_object_config_list('last_fetch_datetime')
|
||||
for last_fetch_datetime in last_fetch_datetime_list:
|
||||
json_file = read_json(last_fetch_datetime)
|
||||
upload_json(json_file, s3_client, CONFIG_BUCKET, f'{LAST_FETCH_DATETIME_INFO_FOLDER}/{path.basename(last_fetch_datetime)}')
|
||||
|
||||
# 環境変数を設定(CRMの認証情報は別途設定しておくこと)
|
||||
set_environment(monkeypatch)
|
||||
# 差分取得用に環境変数を設定
|
||||
monkeypatch.setattr('src.aws.s3.OBJECT_INFO_FILENAME', 'crm_object_list_diff.json')
|
||||
# 一気通貫テスト用に件数の制限をかける
|
||||
monkeypatch.setattr('src.salesforce.soql_builder.FETCH_LIMIT_CLAUSE', ' LIMIT 10')
|
||||
# 件数取得はログ出力用なので、0件が返るようにする
|
||||
monkeypatch.setattr('src.fetch_crm_data_process.fetch_record_count_retry', lambda x, y, z: 10)
|
||||
# ログレベルをDEBUGに
|
||||
monkeypatch.setattr('src.util.logger.LOG_LEVEL', 'DEBUG')
|
||||
# 実行日時を固定する
|
||||
now = datetime.now(timezone.utc).strftime(YYYYMMDDTHHMMSSTZ)
|
||||
format_now = datetime.strptime(now, YYYYMMDDTHHMMSSTZ).strftime('%Y%m%d%H%M%S')
|
||||
path_now = datetime.strptime(now, YYYYMMDDTHHMMSSTZ).strftime('%Y/%m/%d/%H%M%S')
|
||||
|
||||
class MockExecuteDateTime(ExecuteDateTime):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._ExecuteDateTime__execute_datetime = now
|
||||
|
||||
monkeypatch.setattr('src.prepare_data_fetch_process.ExecuteDateTime', MockExecuteDateTime)
|
||||
|
||||
# Act
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.info(f'##########################')
|
||||
logger.info(f'# 差分データ取得処理:実行開始 #')
|
||||
logger.info(f'##########################')
|
||||
controller()
|
||||
logger.info(f'##########################')
|
||||
logger.info(f'# 差分データ取得処理:実行終了 #')
|
||||
logger.info(f'##########################')
|
||||
# Assertion
|
||||
log_messages = caplog.messages
|
||||
# ループ前のログ確認
|
||||
assert 'I-CTRL-01 CRMデータ取得処理を開始します' in log_messages
|
||||
assert 'I-CTRL-02 データ取得準備処理呼び出し' in log_messages
|
||||
assert_prepare_process_log(log_messages, now, path.basename(object_info_list[0]))
|
||||
assert 'I-CTRL-03 取得対象オブジェクトのループ処理開始' in log_messages
|
||||
# オブジェクト情報を取得する(diff)
|
||||
object_info_list = object_info_files[0]
|
||||
|
||||
for object_info in object_info_list['objects']:
|
||||
target_object_name = object_info['object_name']
|
||||
upload_file_name = f'CRM_{target_object_name}_{format_now}'
|
||||
assert 'I-CTRL-05 オブジェクト情報形式チェック処理呼び出し' in log_messages
|
||||
assert_check_process_log(log_messages)
|
||||
assert f'I-CTRL-06 [{target_object_name}]のデータ取得を開始します' in log_messages
|
||||
assert f'I-CTRL-08 [{target_object_name}]のデータ取得期間設定処理呼び出し' in log_messages
|
||||
assert_period_process_log(log_messages, target_object_name, target_object_name, now)
|
||||
assert f'I-CTRL-09 [{target_object_name}]のデータ取得処理呼び出し' in log_messages
|
||||
assert_fetch_process_log(log_messages, target_object_name)
|
||||
assert f'I-CTRL-10 [{target_object_name}] の出力ファイル名は [{upload_file_name}] となります' in log_messages
|
||||
assert f'I-CTRL-11 [{target_object_name}] CRM電文データバックアップ処理呼び出し' in log_messages
|
||||
assert_backup_response_process_log(log_messages, target_object_name)
|
||||
assert_file_exist(s3_client, BACKUP_BUCKET, f'{BACKUP_RESPONSE_JSON_FOLDER}/{path_now}/{upload_file_name}.json')
|
||||
assert f'I-CTRL-12 [{target_object_name}] CSV変換処理呼び出し' in log_messages
|
||||
assert_convert_process_log(log_messages, target_object_name)
|
||||
assert f'I-CTRL-13 [{target_object_name}] CSVデータバックアップ処理呼び出し' in log_messages
|
||||
assert_backup_csv_process_log(log_messages, target_object_name, upload_file_name)
|
||||
assert_file_exist(s3_client, BACKUP_BUCKET, f'{BACKUP_DATA_IMPORT_FOLDER}/{path_now}/{upload_file_name}.csv')
|
||||
assert f'I-CTRL-14 [{target_object_name}] CSVデータアップロード処理呼び出し' in log_messages
|
||||
assert_upload_csv_process_log(log_messages, target_object_name, upload_file_name)
|
||||
assert_file_exist(s3_client, DATA_BUCKET, f'{TARGET_FOLDER}/{upload_file_name}.csv')
|
||||
assert f'I-CTRL-15 [{target_object_name}] 前回取得日時ファイル更新処理呼び出し' in log_messages
|
||||
assert_upload_fetch_datetime_process_log(log_messages, target_object_name, False)
|
||||
assert_file_exist(s3_client, CONFIG_BUCKET, f'{LAST_FETCH_DATETIME_INFO_FOLDER}/{target_object_name}.json')
|
||||
assert f'I-CTRL-16 [{target_object_name}] 処理正常終了' in log_messages
|
||||
|
||||
# ループ終了後のログの確認
|
||||
process_result_json = {obj['object_name']: 'success' for obj in object_info_list['objects']}
|
||||
assert f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{process_result_json}]' in log_messages
|
||||
assert f'I-CTRL-18 CRM_取得処理実施結果ファイルアップロード処理開始' in log_messages
|
||||
assert_file_exist(s3_client, BACKUP_BUCKET, f'{BACKUP_DATA_IMPORT_FOLDER}/{path_now}/{PROCESS_RESULT_JSON_FILE_NAME}')
|
||||
assert f'I-CTRL-19 すべてのデータの取得に成功しました' in log_messages
|
||||
assert f'I-CTRL-20 CRMデータ取得処理を終了します' in log_messages
|
||||
|
||||
# 全件要取得処理を実行する
|
||||
|
||||
# 全件取得用に環境変数を設定
|
||||
monkeypatch.setattr('src.aws.s3.OBJECT_INFO_FILENAME', 'crm_object_list_all.json')
|
||||
logger.info(f'##########################')
|
||||
logger.info(f'# 全件データ取得処理:実行開始 #')
|
||||
logger.info(f'##########################')
|
||||
controller()
|
||||
logger.info(f'##########################')
|
||||
logger.info(f'# 全件データ取得処理:実行終了 #')
|
||||
logger.info(f'##########################')
|
||||
# ログ再取得
|
||||
log_messages_all = caplog.messages
|
||||
object_info_list_all = object_info_files[1]
|
||||
# 開始ログなどはテスト済みなのでチェックを省く
|
||||
for object_info in object_info_list_all['objects']:
|
||||
target_object_name = object_info['object_name']
|
||||
upload_file_name = f'CRM_{target_object_name}_ALL_{format_now}'
|
||||
datetime_filename = f'{target_object_name}_ALL'
|
||||
assert 'I-CTRL-05 オブジェクト情報形式チェック処理呼び出し' in log_messages_all
|
||||
assert_check_process_log(log_messages_all)
|
||||
assert f'I-CTRL-06 [{target_object_name}]のデータ取得を開始します' in log_messages_all
|
||||
assert f'I-CTRL-08 [{target_object_name}]のデータ取得期間設定処理呼び出し' in log_messages_all
|
||||
assert_period_process_log(log_messages_all, target_object_name, datetime_filename, now)
|
||||
assert f'I-CTRL-09 [{target_object_name}]のデータ取得処理呼び出し' in log_messages_all
|
||||
assert_fetch_process_log(log_messages_all, target_object_name)
|
||||
assert f'I-CTRL-10 [{target_object_name}] の出力ファイル名は [{upload_file_name}] となります' in log_messages_all
|
||||
assert f'I-CTRL-11 [{target_object_name}] CRM電文データバックアップ処理呼び出し' in log_messages_all
|
||||
assert_backup_response_process_log(log_messages_all, target_object_name)
|
||||
assert_file_exist(s3_client, BACKUP_BUCKET, f'{BACKUP_RESPONSE_JSON_FOLDER}/{path_now}/{upload_file_name}.json')
|
||||
assert f'I-CTRL-12 [{target_object_name}] CSV変換処理呼び出し' in log_messages_all
|
||||
assert_convert_process_log(log_messages_all, target_object_name)
|
||||
assert f'I-CTRL-13 [{target_object_name}] CSVデータバックアップ処理呼び出し' in log_messages_all
|
||||
assert_backup_csv_process_log(log_messages_all, target_object_name, upload_file_name)
|
||||
assert_file_exist(s3_client, BACKUP_BUCKET, f'{BACKUP_DATA_IMPORT_FOLDER}/{path_now}/{upload_file_name}.csv')
|
||||
assert f'I-CTRL-14 [{target_object_name}] CSVデータアップロード処理呼び出し' in log_messages_all
|
||||
assert_upload_csv_process_log(log_messages_all, target_object_name, upload_file_name)
|
||||
assert_file_exist(s3_client, DATA_BUCKET, f'{TARGET_FOLDER}/{upload_file_name}.csv')
|
||||
assert f'I-CTRL-15 [{target_object_name}] 前回取得日時ファイル更新処理呼び出し' in log_messages_all
|
||||
assert_upload_fetch_datetime_process_log(log_messages_all, target_object_name, True)
|
||||
assert_file_exist(s3_client, CONFIG_BUCKET, f'{LAST_FETCH_DATETIME_INFO_FOLDER}/{target_object_name}.json')
|
||||
assert f'I-CTRL-16 [{target_object_name}] 処理正常終了' in log_messages_all
|
||||
|
||||
|
||||
"""
|
||||
以下、アサーション関数
|
||||
"""
|
||||
|
||||
|
||||
def assert_prepare_process_log(log_messages, now, object_info_list_filename):
|
||||
assert 'I-PRE-01 データ取得準備処理を開始します' in log_messages
|
||||
assert f'I-PRE-02 データ取得処理開始日時:{now}' in log_messages
|
||||
assert f'I-PRE-03 CRM_取得オブジェクト情報ファイルの取得開始します ファイルパス:[s3://{CONFIG_BUCKET}/{OBJECT_INFO_FOLDER}/{object_info_list_filename}]' in log_messages
|
||||
assert 'I-PRE-09 データ取得準備処理を終了します' in log_messages
|
||||
|
||||
|
||||
def assert_check_process_log(log_messages):
|
||||
assert 'I-CHK-01 オブジェクト情報形式チェック処理を開始します' in log_messages
|
||||
assert 'I-CHK-02 オブジェクト情報形式チェック処理を終了します' in log_messages
|
||||
|
||||
|
||||
def assert_period_process_log(log_messages, target_object_name, datetime_file_name, now):
|
||||
assert f'I-DATE-01 [{target_object_name}] のデータ取得期間設定処理を開始します' in log_messages
|
||||
assert f'I-DATE-02 前回取得日時ファイルの取得開始します ファイルパス:[s3://{CONFIG_BUCKET}/{LAST_FETCH_DATETIME_INFO_FOLDER}/{datetime_file_name}.json]' in log_messages
|
||||
assert 'I-DATE-03 前回取得日時ファイルの取得成功しました' in log_messages
|
||||
assert f'I-DATE-06 取得範囲 From: [1900-01-01T00:00:00.000Z] To: [{now}]' in log_messages
|
||||
assert f'I-DATE-07 [{target_object_name}] のデータ取得期間設定処理を終了します' in log_messages
|
||||
|
||||
|
||||
def assert_fetch_process_log(log_messages, target_object_name):
|
||||
assert f'I-FETCH-01 [{target_object_name}] のCRMからのデータ取得処理を開始します' in log_messages
|
||||
assert f'I-FETCH-02 [{target_object_name}] の件数取得を開始します' in log_messages
|
||||
assert f'I-FETCH-03 [{target_object_name}] の件数:[10]' in log_messages
|
||||
assert f'I-FETCH-04 [{target_object_name}] のレコード取得を開始します' in log_messages
|
||||
assert f'I-FETCH-05 [{target_object_name}] のレコード取得が成功しました' in log_messages
|
||||
assert f'I-FETCH-06 [{target_object_name}] のCRMからのデータ取得処理を終了します' in log_messages
|
||||
|
||||
|
||||
def assert_backup_response_process_log(log_messages, target_object_name):
|
||||
assert f'I-RESBK-01 [{target_object_name}] のCRM電文データバックアップ処理を開始します' in log_messages
|
||||
assert f'I-RESBK-03 [{target_object_name}] のCRM電文データバックアップ処理を終了します' in log_messages
|
||||
|
||||
|
||||
def assert_convert_process_log(log_messages, target_object_name):
|
||||
assert f'I-CONV-01 [{target_object_name}] のCSV変換処理を開始します' in log_messages
|
||||
assert f'I-CONV-03 [{target_object_name}] のCSV変換処理を終了します' in log_messages
|
||||
|
||||
|
||||
def assert_backup_csv_process_log(log_messages, target_object_name, upload_file_name):
|
||||
assert f'I-CSVBK-01 [{target_object_name}] のCSVデータのバックアップ処理を開始します ファイル名:[{upload_file_name}.csv]' in log_messages
|
||||
assert f'I-CSVBK-03 [{target_object_name}] のCSVデータのバックアップ処理を終了します' in log_messages
|
||||
|
||||
|
||||
def assert_upload_csv_process_log(log_messages, target_object_name, upload_file_name):
|
||||
assert f'I-UPLD-01 [{target_object_name}] のCSVデータアップロード処理を開始します ファイル名:[{upload_file_name}.csv]' in log_messages
|
||||
assert f'I-UPLD-03 [{target_object_name}] のCSVデータのアップロード処理を終了します' in log_messages
|
||||
|
||||
|
||||
def assert_upload_fetch_datetime_process_log(log_messages, target_object_name, is_all=False):
|
||||
assert f'I-UPD-01 [{target_object_name}] の前回取得日時ファイルの更新処理を開始します' in log_messages
|
||||
if is_all:
|
||||
assert f'I-UPD-02 [{target_object_name}] の前回取得日時ファイルの更新処理をスキップします' in log_messages
|
||||
return
|
||||
assert f'I-UPD-04 [{target_object_name}] の前回取得日時ファイルの更新処理を終了します' in log_messages
|
||||
|
||||
|
||||
def assert_file_exist(s3_client, bucket_name, file_key):
|
||||
try:
|
||||
assert s3_client.head_object(Bucket=bucket_name, Key=file_key) is not None
|
||||
except Exception:
|
||||
raise Exception(f'ファイルが存在しません バケット名:{bucket_name}, ファイルパス:{file_key}')
|
||||
|
||||
|
||||
"""
|
||||
以下、取得準備関数
|
||||
"""
|
||||
|
||||
|
||||
def get_object_config_list(folder_name: str):
|
||||
local_s3_path = path.join(ROOT_DIR, '..', '..', 's3', 'config', 'crm', folder_name)
|
||||
config_list = [os.path.join(local_s3_path, config) for config in os.listdir(local_s3_path) if config.endswith('.json')]
|
||||
return sorted(config_list, reverse=True)
|
||||
|
||||
|
||||
def read_json(json_path):
|
||||
with open(json_path, 'r', encoding='utf8') as f:
|
||||
json_str = f.read()
|
||||
JsonParser
|
||||
json_file = JsonParser(json_str).parse()
|
||||
|
||||
return json_file
|
||||
|
||||
|
||||
def upload_json(json_file, s3_client, bucket, folder):
|
||||
json_str = json.dumps(json_file)
|
||||
s3_client.put_object(Bucket=bucket, Key=folder, Body=json_str)
|
||||
|
||||
|
||||
def set_environment(monkeypatch):
|
||||
# 環境変数を設定(CRMの認証情報は別途設定しておくこと)
|
||||
monkeypatch.setattr('src.aws.s3.IMPORT_DATA_BUCKET', DATA_BUCKET)
|
||||
monkeypatch.setattr('src.aws.s3.CRM_IMPORT_DATA_FOLDER', TARGET_FOLDER)
|
||||
monkeypatch.setattr('src.aws.s3.CRM_CONFIG_BUCKET', CONFIG_BUCKET)
|
||||
monkeypatch.setattr('src.aws.s3.OBJECT_INFO_FOLDER', OBJECT_INFO_FOLDER)
|
||||
monkeypatch.setattr('src.aws.s3.LAST_FETCH_DATE_FOLDER', LAST_FETCH_DATETIME_INFO_FOLDER)
|
||||
monkeypatch.setattr('src.aws.s3.CRM_BACKUP_BUCKET', BACKUP_BUCKET)
|
||||
monkeypatch.setattr('src.upload_result_data_process.PROCESS_RESULT_FILENAME', PROCESS_RESULT_JSON_FILE_NAME)
|
||||
monkeypatch.setattr('src.copy_crm_csv_data_process.CRM_IMPORT_DATA_BACKUP_FOLDER', BACKUP_DATA_IMPORT_FOLDER)
|
||||
monkeypatch.setattr('src.set_datetime_period_process.CRM_CONFIG_BUCKET', CONFIG_BUCKET)
|
||||
monkeypatch.setattr('src.aws.s3.CRM_IMPORT_DATA_BACKUP_FOLDER', BACKUP_DATA_IMPORT_FOLDER)
|
||||
monkeypatch.setattr('src.aws.s3.PROCESS_RESULT_FOLDER', BACKUP_DATA_IMPORT_FOLDER)
|
||||
monkeypatch.setattr('src.aws.s3.RESPONSE_JSON_BACKUP_FOLDER', BACKUP_RESPONSE_JSON_FOLDER)
|
||||
monkeypatch.setattr('src.prepare_data_fetch_process.CRM_CONFIG_BUCKET', CONFIG_BUCKET)
|
||||
213
ecs/crm-datafetch/tests/util/test_counter_object.py
Normal file
213
ecs/crm-datafetch/tests/util/test_counter_object.py
Normal file
@ -0,0 +1,213 @@
|
||||
import pytest
|
||||
from src.util.counter_object import CounterObject
|
||||
|
||||
|
||||
class TestCounterObject:
|
||||
|
||||
def test_constructor(self) -> int:
|
||||
"""
|
||||
Cases:
|
||||
カウンターオブジェクト生成時に、数値を渡すと例外が発生しないこと
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
例外が発生しないこと
|
||||
"""
|
||||
# Act
|
||||
CounterObject(1)
|
||||
|
||||
# Expects
|
||||
pass
|
||||
|
||||
def test_constructor_string_number(self) -> int:
|
||||
"""
|
||||
Cases:
|
||||
カウンターオブジェクト生成時に、文字列型の数値を渡すと例外が発生しないこと
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
例外が発生しないこと
|
||||
"""
|
||||
# Act
|
||||
CounterObject("1")
|
||||
|
||||
# Expects
|
||||
pass
|
||||
|
||||
def test_raise_constructor_string(self) -> int:
|
||||
"""
|
||||
Cases:
|
||||
カウンターオブジェクト生成時に、文字列を渡すと例外が発生すること
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
発生した例外が期待値と一致すること
|
||||
"""
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
CounterObject("test1")
|
||||
|
||||
# Expects
|
||||
assert "invalid literal for int() with base 10:" in str(e.value)
|
||||
|
||||
def test_describe(self) -> int:
|
||||
"""
|
||||
Cases:
|
||||
カウンターオブジェクトにて保持した値を返すこと(インスタンス生成時引数なし)
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
問い合わせた値が期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = CounterObject()
|
||||
actual = sut.describe()
|
||||
|
||||
# Expects
|
||||
assert actual == 1
|
||||
|
||||
def test_describe_argument(self) -> int:
|
||||
"""
|
||||
Cases:
|
||||
カウンターオブジェクトにて保持した値を返すこと(インスタンス生成時引数あり)
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
問い合わせた値が期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = CounterObject(3)
|
||||
actual = sut.describe()
|
||||
|
||||
# Expects
|
||||
assert actual == 3
|
||||
|
||||
def test_raise_describe(self) -> int:
|
||||
"""
|
||||
Cases:
|
||||
カウンターオブジェクトの保持した値を問い合わせる際、引数を渡すと例外が発生すること
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
問い合わせた値が期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
sut = CounterObject()
|
||||
sut.describe(1)
|
||||
|
||||
# Expects
|
||||
assert str(e.value) == 'describe() takes 1 positional argument but 2 were given'
|
||||
|
||||
def test_increment(self) -> int:
|
||||
"""
|
||||
Cases:
|
||||
カウンターオブジェクトにて保持した値がインクリメントされていること(引数なし)
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
戻り値が期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = CounterObject()
|
||||
sut.increment()
|
||||
actual = sut.increment()
|
||||
|
||||
# Expects
|
||||
assert actual == 3
|
||||
|
||||
def test_increment_argument(self) -> int:
|
||||
"""
|
||||
Cases:
|
||||
カウンターオブジェクトにて保持した値がインクリメントされていること(引数あり)
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
戻り値が期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = CounterObject(5)
|
||||
sut.increment(2)
|
||||
actual = sut.increment(2)
|
||||
|
||||
# Expects
|
||||
assert actual == 9
|
||||
|
||||
def test_raise_increment(self) -> int:
|
||||
"""
|
||||
Cases:
|
||||
文字列を引数で渡すことで、例外が発生すること
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
発生した例外が期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
sut = CounterObject(5)
|
||||
sut.increment('aaa')
|
||||
sut.increment('aaa')
|
||||
|
||||
# Expects
|
||||
assert str(e.value) == "unsupported operand type(s) for +=: 'int' and 'str'"
|
||||
|
||||
def test_decrement(self) -> int:
|
||||
"""
|
||||
Cases:
|
||||
カウンターオブジェクトにて保持した値がデクリメントされていること(引数なし)
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
戻り値が期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = CounterObject()
|
||||
sut.decrement()
|
||||
actual = sut.decrement()
|
||||
|
||||
# Expects
|
||||
assert actual == -1
|
||||
|
||||
def test_decrement_argument(self) -> int:
|
||||
"""
|
||||
Cases:
|
||||
カウンターオブジェクトにて保持した値がデクリメントされていること(引数あり)
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
戻り値が期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
sut = CounterObject(5)
|
||||
sut.decrement(2)
|
||||
actual = sut.decrement(2)
|
||||
|
||||
# Expects
|
||||
assert actual == 1
|
||||
|
||||
def test_raise_decrement(self) -> int:
|
||||
"""
|
||||
Cases:
|
||||
文字列を引数で渡すことで、例外が発生すること
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
発生した例外が期待値と一致する
|
||||
"""
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
sut = CounterObject(5)
|
||||
sut.decrement('aaa')
|
||||
sut.decrement('aaa')
|
||||
|
||||
# Expects
|
||||
assert str(e.value) == "unsupported operand type(s) for -=: 'int' and 'str'"
|
||||
361
ecs/crm-datafetch/tests/util/test_dict_checker.py
Normal file
361
ecs/crm-datafetch/tests/util/test_dict_checker.py
Normal file
@ -0,0 +1,361 @@
|
||||
import pytest
|
||||
from src.util.dict_checker import DictChecker
|
||||
|
||||
|
||||
class TestDictChecker:
|
||||
|
||||
@pytest.mark.parametrize('test_data,expect', [
|
||||
('', True),
|
||||
(None, True),
|
||||
('test_data', False),
|
||||
])
|
||||
def test_is_empty(self, test_data, expect):
|
||||
"""
|
||||
Cases:
|
||||
1. 辞書型データの対象のキーの値が''(空文字)の場合、Trueが返る
|
||||
2. 辞書型データの対象のキーの値がNoneの場合、Trueが返る
|
||||
3. 辞書型データの対象のキーの値が存在する場合、Falseが返る
|
||||
Arranges:
|
||||
- 辞書型のオブジェクト情報を準備する
|
||||
- 辞書型チェックインスタンスを生成する
|
||||
Expects:
|
||||
- 戻り値が期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_dict = {
|
||||
"test_key": test_data
|
||||
}
|
||||
|
||||
sut = DictChecker(object_dict)
|
||||
|
||||
# Act
|
||||
actual = sut.is_empty("test_key")
|
||||
|
||||
# Expects
|
||||
assert actual is expect
|
||||
|
||||
@pytest.mark.parametrize('test_data,expect', [
|
||||
([''], False),
|
||||
([None], False),
|
||||
(['test_value'], False),
|
||||
([], True),
|
||||
('test_value', False)
|
||||
])
|
||||
def test_is_list_empty(self, test_data, expect):
|
||||
"""
|
||||
Cases:
|
||||
1. 辞書型のデータの対象のキーのリストの値が''(空文字)の場合、Falseが返る
|
||||
2. 辞書型データの対象のキーのリストの値がNoneの場合、Falseが返る
|
||||
3. 辞書型データの対象のキーのリストの値が存在する場合、Falseが返る
|
||||
4. 辞書型データの対象のキーのリストの値が[](空)の場合、Trueが返る
|
||||
5. 辞書型データの対象のキーの値に文字列が存在する場合、Falseが返る
|
||||
Arranges:
|
||||
- 辞書型のオブジェクト情報を準備する
|
||||
- 辞書型チェックインスタンスを生成する
|
||||
Expects:
|
||||
- 戻り値が期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_dict = {
|
||||
"test_key": test_data
|
||||
}
|
||||
|
||||
sut = DictChecker(object_dict)
|
||||
|
||||
# Act
|
||||
actual = sut.is_list_empty("test_key")
|
||||
|
||||
# Expects
|
||||
assert actual is expect
|
||||
|
||||
@pytest.mark.parametrize('test_data,expect', [
|
||||
({"test_key": "test_value"}, True),
|
||||
({}, False),
|
||||
({"test_key": ""}, False),
|
||||
])
|
||||
def test_check_key_exist(self, test_data, expect) -> bool:
|
||||
"""
|
||||
Cases:
|
||||
1. 辞書型データの対象のキーが存在する、かつ値が存在する場合、Trueが返る
|
||||
2. 辞書型データの対象のキーが存在しない場合、Falseが返る
|
||||
3. 辞書型データの対象のキーの値が存在しない場合、Falseが返る
|
||||
Arranges:
|
||||
- 辞書型のオブジェクト情報を準備する
|
||||
- 辞書型チェックインスタンスを生成する
|
||||
Expects:
|
||||
- 戻り値が期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_dict = test_data
|
||||
|
||||
sut = DictChecker(object_dict)
|
||||
|
||||
# Act
|
||||
actual = sut.check_key_exist("test_key")
|
||||
|
||||
# Expects
|
||||
assert actual is expect
|
||||
|
||||
@pytest.mark.parametrize('test_data,test_type,expect', [
|
||||
({"test_key": "test_value"}, str, True),
|
||||
({"test_key": 1}, str, False)
|
||||
])
|
||||
def test_check_data_type(self, test_data, test_type, expect) -> bool:
|
||||
"""
|
||||
Cases:
|
||||
1. 辞書型データの対象のキーの値の型が指定した型と一致した場合、Trueが返る
|
||||
2. 辞書型データの対象のキーの値の型が指定した型と一致しない場合、Falseが返る
|
||||
Arranges:
|
||||
- 辞書型のオブジェクト情報を準備する
|
||||
- 辞書型チェックインスタンスを生成する
|
||||
Expects:
|
||||
- 戻り値が期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_dict = test_data
|
||||
|
||||
sut = DictChecker(object_dict)
|
||||
|
||||
# Act
|
||||
actual = sut.check_data_type("test_key", test_type)
|
||||
|
||||
# Expects
|
||||
assert actual is expect
|
||||
|
||||
@pytest.mark.parametrize('test_data,expect', [
|
||||
({"test_key": "2022-08-01T10:00:00.000Z"}, True),
|
||||
({"test_key": "test_value"}, False)
|
||||
])
|
||||
def test_check_match_pattern(self, test_data, expect) -> bool:
|
||||
"""
|
||||
Cases:
|
||||
1. 辞書型データの対象のキーの値の型が指定した正規表現と一致した場合、Trueが返る
|
||||
2. 辞書型データの対象のキーの値の型が指定した正規表現と一致しない場合、Falseが返る
|
||||
Arranges:
|
||||
- 辞書型のオブジェクト情報を準備する
|
||||
- 辞書型チェックインスタンスを生成する
|
||||
- 正規表現パターンの生成
|
||||
Expects:
|
||||
- 戻り値が期待値と一致する
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_dict = test_data
|
||||
|
||||
sut = DictChecker(object_dict)
|
||||
|
||||
pattern = r'[12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\.000Z'
|
||||
|
||||
# Act
|
||||
actual = sut.check_match_pattern(pattern, "test_key")
|
||||
|
||||
# Expects
|
||||
assert actual is expect
|
||||
|
||||
def test_assert_key_exist(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
辞書型データの対象のキーの値が存在する場合、例外が発生しないこと
|
||||
Arranges:
|
||||
- 辞書型のオブジェクト情報を準備する
|
||||
- 辞書型チェックインスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生しないこと
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_dict = {
|
||||
"test_key": "test_value"
|
||||
}
|
||||
|
||||
sut = DictChecker(object_dict)
|
||||
|
||||
# Act
|
||||
sut.assert_key_exist("test_key")
|
||||
|
||||
# Expects
|
||||
pass
|
||||
|
||||
def test_raise_assert_key_exist(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
辞書型データの対象のキーの値が存在しない場合、例外が発生すること
|
||||
Arranges:
|
||||
- 辞書型のオブジェクト情報を準備する
|
||||
- 辞書型チェックインスタンスを生成する
|
||||
Expects:
|
||||
- 発生した例外が期待値と一致すること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_dict = {
|
||||
"test_key": ""
|
||||
}
|
||||
|
||||
sut = DictChecker(object_dict)
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
sut.assert_key_exist("test_key")
|
||||
|
||||
# Expects
|
||||
assert str(e.value) == '「test_key」キーは必須です'
|
||||
|
||||
def test_assert_data_type(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
辞書型データの対象のキーの値の型が指定した型と一致した場合、例外が発生しないこと
|
||||
Arranges:
|
||||
- 辞書型のオブジェクト情報を準備する
|
||||
- 辞書型チェックインスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生しないこと
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_dict = {
|
||||
"test_key": "test_value"
|
||||
}
|
||||
|
||||
sut = DictChecker(object_dict)
|
||||
|
||||
# Act
|
||||
sut.assert_data_type("test_key", str)
|
||||
|
||||
# Expects
|
||||
pass
|
||||
|
||||
def test_raise_assert_data_type(self) -> None:
|
||||
"""
|
||||
Cases:
|
||||
辞書型データの対象のキーの値の型が指定した型と一致しない場合、例外が発生すること
|
||||
Arranges:
|
||||
- 辞書型のオブジェクト情報を準備する
|
||||
- 辞書型チェックインスタンスを生成する
|
||||
Expects:
|
||||
- 発生した例外が期待値と一致すること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_dict = {
|
||||
"test_key": 1
|
||||
}
|
||||
|
||||
sut = DictChecker(object_dict)
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
sut.assert_data_type("test_key", str)
|
||||
|
||||
# Expects
|
||||
assert str(e.value) == "「test_key」キーの値は「<class 'str'>」でなければなりません"
|
||||
|
||||
def test_assert_match_pattern(self):
|
||||
"""
|
||||
Cases:
|
||||
辞書型データの対象のキーの値の型が指定した正規表現と一致した場合、例外が発生しないこと
|
||||
Arranges:
|
||||
- 辞書型のオブジェクト情報を準備する
|
||||
- 辞書型チェックインスタンスを生成する
|
||||
- 正規表現パターンの生成
|
||||
Expects:
|
||||
- 例外が発生しないこと
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_dict = {
|
||||
"test_key": "2022-08-01T10:00:00.000Z"
|
||||
}
|
||||
|
||||
sut = DictChecker(object_dict)
|
||||
|
||||
pattern = r'[12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\.000Z'
|
||||
|
||||
# Act
|
||||
sut.assert_match_pattern("test_key", pattern, 'YYYY-MM-DDTHH:MM:SS.000Z')
|
||||
|
||||
# Expects
|
||||
pass
|
||||
|
||||
def test_raise_assert_match_pattern(self):
|
||||
"""
|
||||
Cases:
|
||||
辞書型データの対象のキーの値の型が指定した正規表現と一致しない場合、例外が発生すること
|
||||
Arranges:
|
||||
- 辞書型のオブジェクト情報を準備する
|
||||
- 辞書型チェックインスタンスを生成する
|
||||
- 正規表現パターンの生成
|
||||
Expects:
|
||||
- 発生した例外が期待値と一致すること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_dict = {
|
||||
"test_key": "test_value"
|
||||
}
|
||||
|
||||
sut = DictChecker(object_dict)
|
||||
|
||||
pattern = r'[12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\.000Z'
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
sut.assert_match_pattern("test_key", pattern, 'YYYY-MM-DDTHH:MM:SS.000Z')
|
||||
|
||||
assert str(e.value) == '「test_key」キーの値の正規表現チェックに失敗しました 「YYYY-MM-DDTHH:MM:SS.000Z」形式である必要があります'
|
||||
|
||||
def test_assert_list_empty(self):
|
||||
"""
|
||||
Cases:
|
||||
辞書型データの対象のキーのリストの値が存在する場合、例外が発生しないこと
|
||||
Arranges:
|
||||
- 辞書型のオブジェクト情報を準備する
|
||||
- 辞書型チェックインスタンスを生成する
|
||||
Expects:
|
||||
- 例外が発生しないこと
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_dict = {
|
||||
"test_key": [
|
||||
"test_value"
|
||||
]
|
||||
}
|
||||
|
||||
sut = DictChecker(object_dict)
|
||||
|
||||
# Act
|
||||
sut.assert_list_empty("test_key")
|
||||
|
||||
# Expects
|
||||
pass
|
||||
|
||||
def test_raise_assert_list_empty(self):
|
||||
"""
|
||||
Cases:
|
||||
辞書型データの対象のキーのリストの値が存在しない場合、例外が発生すること
|
||||
Arranges:
|
||||
- 辞書型のオブジェクト情報を準備する
|
||||
- 辞書型チェックインスタンスを生成する
|
||||
Expects:
|
||||
- 発生した例外が期待値と一致すること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
object_dict = {
|
||||
"test_key": [
|
||||
]
|
||||
}
|
||||
|
||||
sut = DictChecker(object_dict)
|
||||
|
||||
# Act
|
||||
with pytest.raises(Exception) as e:
|
||||
sut.assert_list_empty("test_key")
|
||||
|
||||
# Expects
|
||||
assert str(e.value) == '「test_key」キーのリストの値は必須です'
|
||||
81
ecs/crm-datafetch/tests/util/test_execute_datetime.py
Normal file
81
ecs/crm-datafetch/tests/util/test_execute_datetime.py
Normal file
@ -0,0 +1,81 @@
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from src.util.execute_datetime import ExecuteDateTime
|
||||
|
||||
|
||||
class TestExecuteDateTime:
|
||||
|
||||
def test_constructor(self):
|
||||
"""
|
||||
Cases:
|
||||
インスタンス生成テスト
|
||||
UTC時刻が文字列で設定されるかのチェック
|
||||
Arranges:
|
||||
- 現在時刻の保持
|
||||
- 期待される値の正規表現作成
|
||||
Expects:
|
||||
- 作成されたクラスが保持している時刻が、期待値と一致すること
|
||||
- 作成されたクラスが保持している時刻が、正規表現と一致すること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
datetime_now = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.000Z')
|
||||
|
||||
pattern = r'[12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\.000Z'
|
||||
|
||||
# Expects
|
||||
sut = ExecuteDateTime()
|
||||
|
||||
assert str(sut) == datetime_now
|
||||
assert re.fullmatch(pattern, str(sut))
|
||||
|
||||
def test_to_path(self) -> str:
|
||||
"""
|
||||
Cases:
|
||||
生成されたパスがYYYYMMDD/HHMMSSの形式で出力されること
|
||||
Arranges:
|
||||
- 現在時刻をパスに変換する
|
||||
- 期待される値の正規表現作成
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
- 戻り値が、正規表現と一致すること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
datetime_now_path = datetime.now(timezone.utc).strftime(
|
||||
'%Y-%m-%dT%H:%M:%S.000Z').rstrip('000Z').translate(str.maketrans({'-': '/', 'T': '/', ':': None, '.': None}))
|
||||
|
||||
pattern = r'[12]\d{3}\/(0[1-9]|1[0-2])\/(0[1-9]|[12][0-9]|3[01])\/([01][0-9]|2[0-3])[0-5][0-9][0-5][0-9]'
|
||||
|
||||
# Expects
|
||||
sut = ExecuteDateTime()
|
||||
actual = sut.to_path()
|
||||
|
||||
assert actual == datetime_now_path
|
||||
assert re.fullmatch(pattern, actual)
|
||||
|
||||
def test_format_date(self) -> str:
|
||||
"""
|
||||
Cases:
|
||||
生成されたパスがYYYYMMDDHHMMSSの形式で出力されること
|
||||
Arranges:
|
||||
- 現在時刻をパスに変換する
|
||||
- 期待される値の正規表現作成
|
||||
Expects:
|
||||
- 戻り値が、期待値と一致する
|
||||
- 戻り値が、正規表現と一致すること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
datetime_now_path = datetime.now(timezone.utc).strftime(
|
||||
'%Y-%m-%dT%H:%M:%S.000Z').rstrip('000Z').translate(str.maketrans({'-': None, 'T': None, ':': None, '.': None}))
|
||||
|
||||
pattern = r'[12]\d{3}(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])([01][0-9]|2[0-3])[0-5][0-9][0-5][0-9]'
|
||||
|
||||
# Expects
|
||||
sut = ExecuteDateTime()
|
||||
actual = sut.format_date()
|
||||
|
||||
assert actual == datetime_now_path
|
||||
assert re.fullmatch(pattern, actual)
|
||||
78
ecs/crm-datafetch/tests/util/test_logger_logger_class.py
Normal file
78
ecs/crm-datafetch/tests/util/test_logger_logger_class.py
Normal file
@ -0,0 +1,78 @@
|
||||
import logging
|
||||
from logging import handlers
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from src.util.logger import Logger
|
||||
|
||||
|
||||
class TestLogger:
|
||||
|
||||
def test_constructor_loglevel_test(self, caplog, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
ログレベルに対象外の文字列を指定した場合、デフォルトであるInfoが指定されること
|
||||
Arranges:
|
||||
- LOG_LEVELの変数を想定外の文字列に置き換える
|
||||
- loggerインスタンスの生成
|
||||
- ログ出力
|
||||
Expects:
|
||||
- 期待値通りのログが出力されること
|
||||
- ログレベルがInfoで設定されていること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
monkeypatch.setattr('src.util.logger.LOG_LEVEL', "test")
|
||||
|
||||
sut = Logger()
|
||||
logger = sut.get_logger()
|
||||
logger.info('infoログ出力')
|
||||
|
||||
# Expects
|
||||
assert ("root", logging.INFO, "infoログ出力") in caplog.record_tuples
|
||||
assert logger.getEffectiveLevel() == logging.INFO
|
||||
|
||||
def test_constructor_hashandlers(self, monkeypatch):
|
||||
"""
|
||||
Cases:
|
||||
生成したloggerインスタンスを利用し、期待値通りのログが出力されること
|
||||
Arranges:
|
||||
- `if not self.__logger.hasHandlers():`の分岐に入るようにモックを準備
|
||||
- loggerインスタンスの生成
|
||||
- ログ出力
|
||||
Expects:
|
||||
- loggerインスタンスに1件以上のハンドラーが存在すること
|
||||
- loggerインスタンスにStreamHandlerが含まれていること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
def dummy_method(arg):
|
||||
return False
|
||||
|
||||
monkeypatch.setattr("logging.Logger.hasHandlers", dummy_method)
|
||||
|
||||
sut = Logger()
|
||||
logger = sut.get_logger()
|
||||
logger.warning('warningログ出力')
|
||||
|
||||
# Expects
|
||||
assert len(logger.handlers) >= 1
|
||||
assert "StreamHandler" in str(logger.handlers)
|
||||
|
||||
def test_get_logger(self, caplog):
|
||||
"""
|
||||
Cases:
|
||||
生成したloggerインスタンスを利用し、期待値通りのログが出力されること
|
||||
Arranges:
|
||||
- loggerインスタンスの生成
|
||||
- ログ出力
|
||||
Expects:
|
||||
- 期待値通りのログが出力されること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
sut = Logger()
|
||||
logger = sut.get_logger()
|
||||
logger.warning('warningログ出力')
|
||||
|
||||
# Expects
|
||||
assert ("root", logging.WARNING, "warningログ出力") in caplog.record_tuples
|
||||
193
ecs/crm-datafetch/tests/util/test_logger_out_of_class.py
Normal file
193
ecs/crm-datafetch/tests/util/test_logger_out_of_class.py
Normal file
@ -0,0 +1,193 @@
|
||||
import logging
|
||||
|
||||
from src.util.logger import logger_instance as logger
|
||||
|
||||
|
||||
class TestLogger:
|
||||
|
||||
def test_logging_debug(self, caplog):
|
||||
"""
|
||||
Cases:
|
||||
Debugレベルを設定したloggerについて、それ以下のログが出力されないこと
|
||||
Arranges:
|
||||
- 各レベルでのログを出力する
|
||||
- ログレベルの設定
|
||||
Expects:
|
||||
- 期待値通りのログが出力されること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
logger.debug('debugログ出力')
|
||||
logger.info('infoログ出力')
|
||||
logger.warning("warningログ出力")
|
||||
logger.error("errorログ出力")
|
||||
logger.critical("criticalログ出力")
|
||||
|
||||
# Expects
|
||||
assert ("root", logging.DEBUG, "debugログ出力") in caplog.record_tuples
|
||||
assert ("root", logging.INFO, "infoログ出力") in caplog.record_tuples
|
||||
assert ("root", logging.WARNING, "warningログ出力") in caplog.record_tuples
|
||||
assert ("root", logging.ERROR, "errorログ出力") in caplog.record_tuples
|
||||
assert ("root", logging.CRITICAL, "criticalログ出力") in caplog.record_tuples
|
||||
|
||||
def test_logging_info(self, caplog):
|
||||
"""
|
||||
Cases:
|
||||
Infoレベルを設定したloggerについて、それ以下のログが出力されないこと
|
||||
Arranges:
|
||||
- 各レベルでのログを出力する
|
||||
- ログレベルの設定
|
||||
Expects:
|
||||
- 期待値通りのログが出力されること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
caplog.set_level(logging.INFO)
|
||||
|
||||
logger.debug('debugログ出力')
|
||||
logger.info('infoログ出力')
|
||||
logger.warning("warningログ出力")
|
||||
logger.error("errorログ出力")
|
||||
logger.critical("criticalログ出力")
|
||||
|
||||
# Expects
|
||||
assert ("root", logging.DEBUG, "debugログ出力") not in caplog.record_tuples
|
||||
assert ("root", logging.INFO, "infoログ出力") in caplog.record_tuples
|
||||
assert ("root", logging.WARNING, "warningログ出力") in caplog.record_tuples
|
||||
assert ("root", logging.ERROR, "errorログ出力") in caplog.record_tuples
|
||||
assert ("root", logging.CRITICAL, "criticalログ出力") in caplog.record_tuples
|
||||
|
||||
def test_logging_warning(self, caplog):
|
||||
"""
|
||||
Cases:
|
||||
Warningレベルを設定したloggerについて、それ以下のログが出力されないこと
|
||||
Arranges:
|
||||
- 各レベルでのログを出力する
|
||||
- ログレベルの設定
|
||||
Expects:
|
||||
- 期待値通りのログが出力されること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
caplog.set_level(logging.WARNING)
|
||||
|
||||
logger.debug('debugログ出力')
|
||||
logger.info('infoログ出力')
|
||||
logger.warning("warningログ出力")
|
||||
logger.error("errorログ出力")
|
||||
logger.critical("criticalログ出力")
|
||||
|
||||
# Expects
|
||||
assert ("root", logging.DEBUG, "debugログ出力") not in caplog.record_tuples
|
||||
assert ("root", logging.INFO, "infoログ出力") not in caplog.record_tuples
|
||||
assert ("root", logging.WARNING, "warningログ出力") in caplog.record_tuples
|
||||
assert ("root", logging.ERROR, "errorログ出力") in caplog.record_tuples
|
||||
assert ("root", logging.CRITICAL, "criticalログ出力") in caplog.record_tuples
|
||||
|
||||
def test_logging_error(self, caplog):
|
||||
"""
|
||||
Cases:
|
||||
Errorレベルを設定したloggerについて、それ以下のログが出力されないこと
|
||||
Arranges:
|
||||
- 各レベルでのログを出力する
|
||||
- ログレベルの設定
|
||||
Expects:
|
||||
- 期待値通りのログが出力されること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
caplog.set_level(logging.ERROR)
|
||||
|
||||
logger.debug('debugログ出力')
|
||||
logger.info('infoログ出力')
|
||||
logger.warning("warningログ出力")
|
||||
logger.error("errorログ出力")
|
||||
logger.critical("criticalログ出力")
|
||||
|
||||
# Expects
|
||||
assert ("root", logging.DEBUG, "debugログ出力") not in caplog.record_tuples
|
||||
assert ("root", logging.INFO, "infoログ出力") not in caplog.record_tuples
|
||||
assert ("root", logging.WARNING, "warningログ出力") not in caplog.record_tuples
|
||||
assert ("root", logging.ERROR, "errorログ出力") in caplog.record_tuples
|
||||
assert ("root", logging.CRITICAL, "criticalログ出力") in caplog.record_tuples
|
||||
|
||||
def test_logging_critical(self, caplog):
|
||||
"""
|
||||
Cases:
|
||||
Criticalレベルを設定したloggerについて、それ以下のログが出力されないこと
|
||||
Arranges:
|
||||
- 各レベルでのログを出力する
|
||||
- ログレベルの設定
|
||||
Expects:
|
||||
- 期待値通りのログが出力されること
|
||||
"""
|
||||
|
||||
# Arranges
|
||||
caplog.set_level(logging.CRITICAL)
|
||||
|
||||
logger.debug('debugログ出力')
|
||||
logger.info('infoログ出力')
|
||||
logger.warning("warningログ出力")
|
||||
logger.error("errorログ出力")
|
||||
logger.critical("criticalログ出力")
|
||||
|
||||
# Expects
|
||||
assert ("root", logging.DEBUG, "debugログ出力") not in caplog.record_tuples
|
||||
assert ("root", logging.INFO, "infoログ出力") not in caplog.record_tuples
|
||||
assert ("root", logging.WARNING, "warningログ出力") not in caplog.record_tuples
|
||||
assert ("root", logging.ERROR, "errorログ出力") not in caplog.record_tuples
|
||||
assert ("root", logging.CRITICAL, "criticalログ出力") in caplog.record_tuples
|
||||
|
||||
def test_logging_getlogger_boto3(self):
|
||||
"""
|
||||
Cases:
|
||||
個別設定したboto3モジュールのログレベル確認
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
設定されているログレベルが期待値と一致すること
|
||||
"""
|
||||
|
||||
# Expects
|
||||
assert logging.getLogger("boto3").getEffectiveLevel() == logging.WARNING
|
||||
|
||||
def test_logging_getlogger_botocore(self):
|
||||
"""
|
||||
Cases:
|
||||
個別設定したbotocoreモジュールのログレベル確認
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
設定されているログレベルが期待値と一致すること
|
||||
"""
|
||||
|
||||
# Expects
|
||||
assert logging.getLogger("botocore").getEffectiveLevel() == logging.WARNING
|
||||
|
||||
def test_logging_getlogger_s3transfer(self):
|
||||
"""
|
||||
Cases:
|
||||
個別設定したs3transferモジュールのログレベル確認
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
設定されているログレベルが期待値と一致すること
|
||||
"""
|
||||
|
||||
# Expects
|
||||
assert logging.getLogger("s3transfer").getEffectiveLevel() == logging.WARNING
|
||||
|
||||
def test_logging_getlogger_urllib3(self):
|
||||
"""
|
||||
Cases:
|
||||
個別設定したurllib3モジュールのログレベル確認
|
||||
Arranges:
|
||||
なし
|
||||
Expects:
|
||||
設定されているログレベルが期待値と一致すること
|
||||
"""
|
||||
|
||||
# Expects
|
||||
assert logging.getLogger("s3transfer").getEffectiveLevel() == logging.WARNING
|
||||
46
rds_mysql/stored_procedure/crm_data_sync.sql
Normal file
46
rds_mysql/stored_procedure/crm_data_sync.sql
Normal file
@ -0,0 +1,46 @@
|
||||
-- A5M2で実行時に[SQL] - [スラッシュ(/)のみの行でSQLを区切る]に変えてから実行する
|
||||
-- $$から始まる文字は後からREPLACEする文字を示す独自ルール
|
||||
-- crm_data_syncストアドプロシージャは、同一セッション内での並列処理を実行することができない
|
||||
CREATE PROCEDURE src02.crm_data_sync(target_table VARCHAR(255), target_table_all VARCHAR(255), target_column VARCHAR(255))
|
||||
BEGIN
|
||||
-- 例外処理
|
||||
-- エラーが発生した場合に一時テーブルの削除を実施
|
||||
DECLARE EXIT HANDLER FOR SQLEXCEPTION
|
||||
BEGIN
|
||||
GET DIAGNOSTICS CONDITION 1
|
||||
@error_state = RETURNED_SQLSTATE, @error_msg = MESSAGE_TEXT;
|
||||
ROLLBACK;
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MYSQL_ERRNO = @error_state, MESSAGE_TEXT = @error_msg;
|
||||
END;
|
||||
|
||||
SET @error_state = NULL, @error_msg = NULL;
|
||||
START TRANSACTION;
|
||||
|
||||
-- ①-1 Salesforce側で物理削除されたデータを検出し更新する
|
||||
SET @update_end_datetime = '
|
||||
UPDATE $$target_table$$ tt
|
||||
SET
|
||||
tt.end_datetime = CURRENT_TIMESTAMP ()
|
||||
, tt.upd_user = CURRENT_USER ()
|
||||
, tt.upd_date = CURRENT_TIMESTAMP ()
|
||||
WHERE
|
||||
tt.end_datetime = "9999-12-31 00:00:00"
|
||||
AND NOT EXISTS (
|
||||
SELECT
|
||||
tta.id
|
||||
FROM
|
||||
$$target_table_all$$ tta
|
||||
WHERE
|
||||
tt.id = tta.id
|
||||
AND tt.$$target_column$$ = tta.$$target_column$$
|
||||
)
|
||||
';
|
||||
SET @update_end_datetime = REPLACE(@update_end_datetime, "$$target_table$$", target_table);
|
||||
SET @update_end_datetime = REPLACE(@update_end_datetime, "$$target_table_all$$", target_table_all);
|
||||
SET @update_end_datetime = REPLACE(@update_end_datetime, "$$target_column$$", target_column);
|
||||
PREPARE update_end_datetime_stmt from @update_end_datetime;
|
||||
EXECUTE update_end_datetime_stmt;
|
||||
|
||||
COMMIT;
|
||||
END
|
||||
93
rds_mysql/stored_procedure/crm_history.sql
Normal file
93
rds_mysql/stored_procedure/crm_history.sql
Normal file
@ -0,0 +1,93 @@
|
||||
-- A5M2で実行時に[SQL] - [スラッシュ(/)のみの行でSQLを区切る]に変えてから実行する
|
||||
-- $$から始まり$$で終わる文字は後からREPLACEする文字を示す独自ルール
|
||||
-- crm_historyストアドプロシージャは、同一セッション内での並列処理を実行することができない
|
||||
CREATE PROCEDURE src02.crm_history(target_table VARCHAR(255), target_column VARCHAR(255))
|
||||
BEGIN
|
||||
-- 例外処理
|
||||
-- エラーが発生した場合に一時テーブルの削除を実施
|
||||
DECLARE EXIT HANDLER FOR SQLEXCEPTION
|
||||
BEGIN
|
||||
GET DIAGNOSTICS CONDITION 1
|
||||
@error_state = RETURNED_SQLSTATE, @error_msg = MESSAGE_TEXT;
|
||||
EXECUTE drop_tmp_table_stmt;
|
||||
DEALLOCATE PREPARE drop_tmp_table_stmt;
|
||||
ROLLBACK;
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MYSQL_ERRNO = @error_state, MESSAGE_TEXT = @error_msg;
|
||||
END;
|
||||
|
||||
SET @error_state = NULL, @error_msg = NULL;
|
||||
START TRANSACTION;
|
||||
|
||||
-- ②-3で利用する一時テーブル削除のSQLを準備
|
||||
-- 例外処理でも利用するため先に記述
|
||||
SET @drop_tmp_table = '
|
||||
DROP TEMPORARY TABLE IF EXISTS $$target_table$$_make_history_tmp
|
||||
';
|
||||
SET @drop_tmp_table = REPLACE(@drop_tmp_table, "$$target_table$$", target_table);
|
||||
PREPARE drop_tmp_table_stmt from @drop_tmp_table;
|
||||
|
||||
-- ①-1 Salesforce側で更新されたデータの適用開始日時と適用終了日時を設定する
|
||||
SET @new_history_save = '
|
||||
UPDATE $$target_table$$
|
||||
SET
|
||||
start_datetime = $$target_column$$
|
||||
, end_datetime = "9999-12-31 00:00:00"
|
||||
, upd_user = CURRENT_USER()
|
||||
, upd_date = CURRENT_TIMESTAMP()
|
||||
WHERE
|
||||
start_datetime IS NULL
|
||||
AND end_datetime IS NULL
|
||||
';
|
||||
SET @new_history_save = REPLACE(@new_history_save, "$$target_table$$", target_table);
|
||||
SET @new_history_save = REPLACE(@new_history_save, "$$target_column$$", target_column);
|
||||
PREPARE new_history_save_stmt from @new_history_save;
|
||||
EXECUTE new_history_save_stmt;
|
||||
DEALLOCATE PREPARE new_history_save_stmt;
|
||||
|
||||
-- ②-1 Salesforce側で更新されたデータを検出用一時テーブルの作成
|
||||
SET @make_history_tmp_create = '
|
||||
CREATE TEMPORARY TABLE $$target_table$$_make_history_tmp
|
||||
SELECT
|
||||
id
|
||||
, MIN($$target_column$$) AS min_start_datetime
|
||||
, MAX(start_datetime) AS max_start_datetime
|
||||
FROM
|
||||
$$target_table$$
|
||||
WHERE
|
||||
end_datetime = "9999-12-31 00:00:00"
|
||||
GROUP BY
|
||||
id
|
||||
HAVING
|
||||
count(id) = 2
|
||||
';
|
||||
SET @make_history_tmp_create = REPLACE(@make_history_tmp_create, "$$target_table$$", target_table);
|
||||
SET @make_history_tmp_create = REPLACE(@make_history_tmp_create, "$$target_column$$", target_column);
|
||||
PREPARE make_history_tmp_create_stmt from @make_history_tmp_create;
|
||||
EXECUTE make_history_tmp_create_stmt;
|
||||
DEALLOCATE PREPARE make_history_tmp_create_stmt;
|
||||
|
||||
-- ②-2 「②-1」で取得した全件に更新処理を行う
|
||||
SET @update_end_datetime = '
|
||||
UPDATE $$target_table$$ tt
|
||||
INNER JOIN $$target_table$$_make_history_tmp mht
|
||||
ON tt.id = mht.id
|
||||
AND tt.start_datetime = mht.min_start_datetime
|
||||
SET
|
||||
end_datetime = mht.max_start_datetime - INTERVAL 1 SECOND
|
||||
, upd_user = CURRENT_USER()
|
||||
, upd_date = CURRENT_TIMESTAMP()
|
||||
';
|
||||
SET @update_end_datetime = REPLACE(@update_end_datetime, "$$target_table$$", target_table);
|
||||
SET @update_end_datetime = REPLACE(@update_end_datetime, "$$target_column$$", target_column);
|
||||
PREPARE update_end_datetime_stmt from @update_end_datetime;
|
||||
EXECUTE update_end_datetime_stmt;
|
||||
DEALLOCATE PREPARE update_end_datetime_stmt;
|
||||
|
||||
-- ②-3 「②-1」で作成した一時テーブルを削除する
|
||||
EXECUTE drop_tmp_table_stmt;
|
||||
DEALLOCATE PREPARE drop_tmp_table_stmt;
|
||||
|
||||
COMMIT;
|
||||
|
||||
END
|
||||
4
s3/config/crm/last_fetch_datetime/Account.json
Normal file
4
s3/config/crm/last_fetch_datetime/Account.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
4
s3/config/crm/last_fetch_datetime/AccountShare.json
Normal file
4
s3/config/crm/last_fetch_datetime/AccountShare.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
4
s3/config/crm/last_fetch_datetime/Call2_vod__c.json
Normal file
4
s3/config/crm/last_fetch_datetime/Call2_vod__c.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
4
s3/config/crm/last_fetch_datetime/Contact.json
Normal file
4
s3/config/crm/last_fetch_datetime/Contact.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"last_fetch_datetime_from": "1900-01-01T00:00:00.000Z",
|
||||
"last_fetch_datetime_to": ""
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user