This PR intends to add soft-record, a way to only record what hasn't changed.

To be able to check whether the tests have changed, we check whether a MD5 hash of the test function's source code has changed from the previous time a recording stored this MD5 to the current test execution.

Any new recording will store this MD5 into the recorded file. To only record the tests that have changed, the user must specify the environment variable TEST_MODE, with value "soft-record".

Fixes #5156
This commit is contained in:
Daniel Rodríguez 2020-02-11 15:19:53 -05:00 коммит произвёл GitHub
Родитель f295a51ab6
Коммит b67506f0a5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
22 изменённых файлов: 593 добавлений и 145 удалений

7
.gitignore поставляемый
Просмотреть файл

@ -134,7 +134,6 @@ dist
dist-*
test-dist
test-dist*
browser
test-browser
typings
typedoc
@ -150,3 +149,9 @@ swagger/*.json
# library-specific ignores
sdk/keyvault/*/src/**/*.js
# skipping browser tests while also ignoring the browser folders where the browser bundles are
/**/browser/*.js
/**/browser/*.js.map
/**/browser/*.html
!/test/browser/*.spec.ts

Просмотреть файл

@ -575,6 +575,12 @@ packages:
dev: false
resolution:
integrity: sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
/@types/md5/2.1.33:
dependencies:
'@types/node': 13.7.0
dev: false
resolution:
integrity: sha512-8+X960EtKLoSblhauxLKy3zzotagjoj3Jt1Tx9oaxUdZEPIBl+mkrUz6PNKpzJgkrKSN9YgkWTA29c0KnLshmA==
/@types/mime-types/2.1.0:
dev: false
resolution:
@ -591,6 +597,18 @@ packages:
dev: false
resolution:
integrity: sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==
/@types/mock-fs/4.10.0:
dependencies:
'@types/node': 13.7.0
dev: false
resolution:
integrity: sha512-FQ5alSzmHMmliqcL36JqIA4Yyn9jyJKvRSGV3mvPh108VFatX7naJDzSG4fnFQNZFq9dIx0Dzoe6ddflMB2Xkg==
/@types/mock-require/2.0.0:
dependencies:
'@types/node': 13.7.0
dev: false
resolution:
integrity: sha512-nOgjoE5bBiDeiA+z41i95makyHUSMWQMOPocP+J67Pqx/68HAXaeWN1NFtrAYYV6LrISIZZ8vKHm/a50k0f6Sg==
/@types/nise/1.4.0:
dev: false
resolution:
@ -3309,6 +3327,10 @@ packages:
dev: false
resolution:
integrity: sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
/get-caller-file/1.0.3:
dev: false
resolution:
integrity: sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
/get-caller-file/2.0.5:
dev: false
engines:
@ -5103,6 +5125,19 @@ packages:
hasBin: true
resolution:
integrity: sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==
/mock-fs/4.10.4:
dev: false
resolution:
integrity: sha512-gDfZDLaPIvtOusbusLinfx6YSe2YpQsDT8qdP41P47dQ/NQggtkHukz7hwqgt8QvMBmAv+Z6DGmXPyb5BWX2nQ==
/mock-require/3.0.3:
dependencies:
get-caller-file: 1.0.3
normalize-path: 2.1.1
dev: false
engines:
node: '>=4.3.0'
resolution:
integrity: sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg==
/moment/2.24.0:
dev: false
resolution:
@ -5249,6 +5284,14 @@ packages:
dev: false
resolution:
integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
/normalize-path/2.1.1:
dependencies:
remove-trailing-separator: 1.1.0
dev: false
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=
/normalize-path/3.0.0:
dev: false
engines:
@ -6210,6 +6253,10 @@ packages:
dev: false
resolution:
integrity: sha512-MXW/jtHyl5F1PZI7NbpS8SOtympdLuF20aoWJT5lELR1p/HJDd5nqW8Eu9uLh/hCRY3FgvrIT5AwDCgBODklcA==
/remove-trailing-separator/1.1.0:
dev: false
resolution:
integrity: sha1-wkvOKig62tW8P1jg1IJJuSN52O8=
/repeating/2.0.1:
dependencies:
is-finite: 1.0.2
@ -8011,7 +8058,7 @@ packages:
dev: false
name: '@rush-temp/abort-controller'
resolution:
integrity: sha512-DO1D00rXpn1zUunQUSSF6VF1CU3nTbSMnoJo4y1y7/HoDjwgvq2Lifu64PscEv8Xs/EqejGGQUldCTlkwJ/wfw==
integrity: sha512-Xla0VK+eEW8lnCzVyAL5++AoKQgNU1zHAUmoHSyMUCMpSG3KaUQiyOpfNyOG29yJ1r9I0z71m30NzRZtHFm39Q==
tarball: 'file:projects/abort-controller.tgz'
version: 0.0.0
'file:projects/ai-text-analytics.tgz':
@ -8071,7 +8118,7 @@ packages:
dev: false
name: '@rush-temp/ai-text-analytics'
resolution:
integrity: sha512-s4AvJH9H5CinaWgZKeL8P2UwpEL7sa5g4J51gAe/Y1AidxkZpqDLlD2m3aR6Ci/ioWwvrgzMCoz/vLvBEp0Frg==
integrity: sha512-fqA10W69RAm4E1lXTZEX3wiCTIYcMG4BfLFL+VwKLdSIewSmK76a52JegKuKEMc6j05twqBDUyIiwbxGV9nG+g==
tarball: 'file:projects/ai-text-analytics.tgz'
version: 0.0.0
'file:projects/app-configuration.tgz':
@ -8113,7 +8160,7 @@ packages:
dev: false
name: '@rush-temp/app-configuration'
resolution:
integrity: sha512-zDI0h0tRBSFYpt6gYADkDF6OIIMgmxlFvtfQ3kg8/87lX14ci06bfDC7AFE7Sjx4qSgLmsWmJRjNSMJgdnMivg==
integrity: sha512-oEvsSxFsumlUtpdWYLzhq20CKy9MmR34FYKbMQrhUBLHAloc+pznexh4u2/3OSdYcugDKgeicwkL35axbgGYKg==
tarball: 'file:projects/app-configuration.tgz'
version: 0.0.0
'file:projects/core-amqp.tgz':
@ -8181,7 +8228,7 @@ packages:
dev: false
name: '@rush-temp/core-amqp'
resolution:
integrity: sha512-9Z/u0i3Cu8SPqpRIWOFwraYSyrhspg/wiDBYgVcwSOHK0hA4oyUA+Y/CgqWtLm+luuTLNNJgRjFveAcxQ4OjTA==
integrity: sha512-MhzxdzNAk3nDK+vSqVlrFWn9WzAlnYZKgixZopFUeLbNXKtTIuknEm4sbDQ8n/1svgzMDWN0PlsWnFYBW0WnBg==
tarball: 'file:projects/core-amqp.tgz'
version: 0.0.0
'file:projects/core-arm.tgz':
@ -8215,7 +8262,7 @@ packages:
dev: false
name: '@rush-temp/core-arm'
resolution:
integrity: sha512-0pn2mZyXmj//Y/nBrZacADv0y5cxhrtTmSDh7lJKBkO1mikLkWocQzfkXVQf2zDoZXRSqK5EDICnRx34Iij6og==
integrity: sha512-gMo5M4yZopVded6wxND2JEGB6LyYP6FPbQYlPyhPFy36scETuxnOzg9d965CYEaLS/pa1Y8m73NvYPntfsFrAg==
tarball: 'file:projects/core-arm.tgz'
version: 0.0.0
'file:projects/core-asynciterator-polyfill.tgz':
@ -8233,7 +8280,7 @@ packages:
dev: false
name: '@rush-temp/core-asynciterator-polyfill'
resolution:
integrity: sha512-jDLggg/4sSiSb9xXQUsivEzh+w65cXOMPe/kPArXFs+LmAHaK1Et7soO3hxgmVW7AZ/5lVp8jcfW3hDXbKIFXQ==
integrity: sha512-wcvU9xzocDcf6JqxBwlUIbDtU8XGBkq8n5TjQ4QYlRSFOUqjEX64a6XdMQr4dcga+OSoZw/QV/12HYPuUypK9w==
tarball: 'file:projects/core-asynciterator-polyfill.tgz'
version: 0.0.0
'file:projects/core-auth.tgz':
@ -8274,7 +8321,7 @@ packages:
dev: false
name: '@rush-temp/core-auth'
resolution:
integrity: sha512-s/939eY3zDHWsLuxyKMR6PfnRaA8fYeoDeAas83xmrRQwU5Nnj2YMfjWl8RwlUrLk4nJ2Hdi2nSZVp2UzCLyuw==
integrity: sha512-SUk7DhjxjZhru6S3LTYJ7ZQjYaTKzrseBnzH632m9cRNiyYKN64ZQy7LlYBuvlFIFUL+6j3unoZlyEThr4SwfA==
tarball: 'file:projects/core-auth.tgz'
version: 0.0.0
'file:projects/core-http.tgz':
@ -8351,7 +8398,7 @@ packages:
dev: false
name: '@rush-temp/core-http'
resolution:
integrity: sha512-Upl8IMRO2gVy5hXFCz3YK0u90oSG2JVuf9fZ8ReWtRYvx9vcAFMvpFMEO0xrZnT7WKzdaT0sBcSs7pe8neHHAQ==
integrity: sha512-Kkoja/RR+gTqQ3X7gjfiPpewtxKfj+tWnUlV95zcjlMQxqzbQ/j6lcH29NHhv52FznxnUhqzUvxYY05cs1cfXQ==
tarball: 'file:projects/core-http.tgz'
version: 0.0.0
'file:projects/core-lro.tgz':
@ -8407,7 +8454,7 @@ packages:
dev: false
name: '@rush-temp/core-lro'
resolution:
integrity: sha512-HzwPk4vzJPaKNDjWArURsZuV7oYXPjVd4ccxHSwTEJ2sSctuSUb3IMXinsNOKjJCHvTYJM6lNkESCuWIyDOTQQ==
integrity: sha512-N+7SM1Yhnch618EhTBBoxQZRddrh+yqQhVuBJV0knDjtiC+nz+coerWGZ77T9tkTSgw4esZtZDn+MdTfQlKY0A==
tarball: 'file:projects/core-lro.tgz'
version: 0.0.0
'file:projects/core-paging.tgz':
@ -8425,7 +8472,7 @@ packages:
dev: false
name: '@rush-temp/core-paging'
resolution:
integrity: sha512-rRAaeqAsySfmVMMf46j6TljIPw2xVRZBVuyU9wrvQKJb1VzIEBZayG1jm9iTyyqUdFOEMn759//IfYP7KkhJNQ==
integrity: sha512-HtQeUV6/jtHxcEJSGOLfZj58FrapAsPl4Lirod7Yib/yXMrDIBYkkkI3Wwx5mGap+6ChPvmsBeskFIii1ZKcJg==
tarball: 'file:projects/core-paging.tgz'
version: 0.0.0
'file:projects/core-tracing.tgz':
@ -8466,7 +8513,7 @@ packages:
dev: false
name: '@rush-temp/core-tracing'
resolution:
integrity: sha512-SJ+L9sNBKGjTh1Ye4OWZ1aqXJ534jIKQwi5eDRa6irbh1jSZsNUmSMnBxzkM3+0kfQZ8YP7jm6sjPykaOJ4hUQ==
integrity: sha512-RWETP32JH6m1gxq4dQKAe572XiKX+bLAlU3/fUxPZPbscLNtphMy/E2DRzyvDCBEQfL+C49MxZIzEoy8ECdKgw==
tarball: 'file:projects/core-tracing.tgz'
version: 0.0.0
'file:projects/cosmos.tgz':
@ -8528,7 +8575,7 @@ packages:
dev: false
name: '@rush-temp/cosmos'
resolution:
integrity: sha512-GRoPrAKiHI521c+rxNT791bVFShNn6YefMT78uX5GqvTkxZa1jY3t5B2IdISXe0w1egUKwqImF2etEza6/pRZw==
integrity: sha512-DKhpoSSi/f/Aq6de+2s/IpX1C5FQbVfBdK063cXTE1SCamCwzYdf6/SLLbWeEVadUgz4wFgBTIy9Ta3Zz79qUQ==
tarball: 'file:projects/cosmos.tgz'
version: 0.0.0
'file:projects/event-hubs.tgz':
@ -8608,7 +8655,7 @@ packages:
dev: false
name: '@rush-temp/event-hubs'
resolution:
integrity: sha512-7Hy1SDsby9EVOhWljTSZ6Z0BWd+YQqtrH5lXeIMxTRxVMP/0IxN2a69X/kSYm2Ey5nJ2Xf6I+lGhNRbJt2xTCw==
integrity: sha512-ADxIBYXDACt52yTU4wn0dcR2pyKLNTlly7yIAD0e43929abQYDaznlMDTzGmjFtfPd3I3ndgc9LThNtse2Ks3A==
tarball: 'file:projects/event-hubs.tgz'
version: 0.0.0
'file:projects/event-processor-host.tgz':
@ -8666,7 +8713,7 @@ packages:
dev: false
name: '@rush-temp/event-processor-host'
resolution:
integrity: sha512-UKQZiZyDo3uX1TvimdPeny6Yc0s0t9foQLx1sap3CMzASBIocWrMXwYmxHWn3n8sRlOmMtqXNyJIy/1zru3+9A==
integrity: sha512-SXJ4EJcxMdnl7GQuy7f7YXS4FLEcmojM48uJKlVQpbdFRSPZczCm8NAA8GGXJmJjVdq+nB388bYk2H724/ZTrA==
tarball: 'file:projects/event-processor-host.tgz'
version: 0.0.0
'file:projects/eventhubs-checkpointstore-blob.tgz':
@ -8731,7 +8778,7 @@ packages:
dev: false
name: '@rush-temp/eventhubs-checkpointstore-blob'
resolution:
integrity: sha512-lxxp1rmltAR87zw3K+swxDYnmXNHEOvxY7mW7cPSekQKfIIlzg/j83ZFluvO7481/HJR9JOabMcFS/VHxhPrgA==
integrity: sha512-gjOffb+0MIjYerROkRE9jsYsEhRK8srYiK+JJbf8Qm4rcmz1cHOnbzO9CecOJ/p0abxOUhWDddaf8v2vPoeftg==
tarball: 'file:projects/eventhubs-checkpointstore-blob.tgz'
version: 0.0.0
'file:projects/identity.tgz':
@ -8787,7 +8834,7 @@ packages:
dev: false
name: '@rush-temp/identity'
resolution:
integrity: sha512-QOB2CcVr5P9knbZUzUVF3N1RpaJzjzwStfMspqmyDmpIdt4YOyXR5510/JuDBztGJE15Qk7m9dMUdnyVO7EoEg==
integrity: sha512-mBOEy+pEAVgeJK+b9LM88IdJEnqqOdrEOIMVdprLpYzAILVVQ2dRLsO+bC5d3TfNK7ZttHz/jRmiGmRDanqobA==
tarball: 'file:projects/identity.tgz'
version: 0.0.0
'file:projects/keyvault-certificates.tgz':
@ -8853,7 +8900,7 @@ packages:
dev: false
name: '@rush-temp/keyvault-certificates'
resolution:
integrity: sha512-rev0ttXaoWIXgv7V0j0bWSlpW6mzJGO8s/FjprXf13cFaHrGL+mIilvY21TQ/PsYtX4H+B4MHzBBWv2rdCr4bg==
integrity: sha512-HWTB3OFSaGPknGpAlUsMwPtznCjrGmQvyi5nwOPFLOGNklQTZIChUu3xlv7u/mm9woH7GDPIHJgFEf1N9kmTmg==
tarball: 'file:projects/keyvault-certificates.tgz'
version: 0.0.0
'file:projects/keyvault-keys.tgz':
@ -8919,7 +8966,7 @@ packages:
dev: false
name: '@rush-temp/keyvault-keys'
resolution:
integrity: sha512-4b5tCgtBSD3JTbMZaxyCAId+4EiRrtCApPIzg3EEZJgc1jXtJoycQbE12D9y9IizuHtHwxOk7fA93o6b61xpsg==
integrity: sha512-hOvqlTGoEqwfvhUq7f/GzbLoWibGlncvQwNuVzkS1nm1/L4ECnJY+nePpj/uC4jZ5fAPiz4B6iFtiWN8RxBDRQ==
tarball: 'file:projects/keyvault-keys.tgz'
version: 0.0.0
'file:projects/keyvault-secrets.tgz':
@ -8985,7 +9032,7 @@ packages:
dev: false
name: '@rush-temp/keyvault-secrets'
resolution:
integrity: sha512-aJVju6YZCSpff4Pu6kT84sbKZirJbEblQNqUM0T3485sohXv7kLiiN2mms7mHr7NlYbXT/KUVmBj8ro5X7w0hw==
integrity: sha512-76XNhCuQ+WVwljPMLrTJhBuu3mD5uBZ/IJB3WaUMpe+CP0t3puLXXoTQXy07+3mKmUcDqISasbVb1wATZfylyg==
tarball: 'file:projects/keyvault-secrets.tgz'
version: 0.0.0
'file:projects/logger.tgz':
@ -9039,7 +9086,7 @@ packages:
dev: false
name: '@rush-temp/logger'
resolution:
integrity: sha512-GCfPJg0YTmoF//OugUXBNudk+xG5zmVM0BDuBPU4aDRqIXGMuLJU6B4bHcSKar+Pp765IaboHZs4RzZuLOH02g==
integrity: sha512-pEEw8s5yGhWD//xsHeJ68St44jvF7ByD6xgdI9MjUfmv86sdCdGb9Nx9SoPK9E3MS917ttPf36uqIfFwnfWs2Q==
tarball: 'file:projects/logger.tgz'
version: 0.0.0
'file:projects/service-bus.tgz':
@ -9120,7 +9167,7 @@ packages:
dev: false
name: '@rush-temp/service-bus'
resolution:
integrity: sha512-3PXValpAas0FSAxghJavB4SUvYBjh1BT+hzUYFfm1xYBaByzuluL0mETOpbH/OJgAEmwZBG/3IplddA1oxcaxQ==
integrity: sha512-GoJsi+/mi4U/jHoL0CEadcHpttm7DzEmnJhZApIEYBA1UqIN8CMZchDwMFSPA0y4ma2oEfZJWCDBPwOU0PRz9w==
tarball: 'file:projects/service-bus.tgz'
version: 0.0.0
'file:projects/storage-blob.tgz':
@ -9181,7 +9228,7 @@ packages:
dev: false
name: '@rush-temp/storage-blob'
resolution:
integrity: sha512-lEP6M8gBMa0lo09vTP23CkzHXpID+MFTP9CABeoCVbl+GTUcagIfPpgwQnYyUkICzkXz7+nK9V8AWoLfaTn0TA==
integrity: sha512-C4qCp2+U6+uWu3X5SKvXYGCYmX5UIuH/TjsQBqBwCr4bkNUxvs90E3QLzh8CS35JcTPt7kLtcg4cZml41IWg7g==
tarball: 'file:projects/storage-blob.tgz'
version: 0.0.0
'file:projects/storage-file-datalake.tgz':
@ -9250,7 +9297,7 @@ packages:
dev: false
name: '@rush-temp/storage-file-datalake'
resolution:
integrity: sha512-f/jaSqr2gkQ6Dlz//6fPvFbRxwRCbIkiq3pL1xkTvFF3c5y1pcF3lG1R48ZhnsSwpKuWDl0vfPEiM/4eWttbHQ==
integrity: sha512-q7y9BkD1DaI1D+4ki8/8Qjsd3T3wikezl5jQnNb6yaBJPPgQqkPX+d9d7QzXfhEzcimJWZ1X4IplyZUu3MxJ9g==
tarball: 'file:projects/storage-file-datalake.tgz'
version: 0.0.0
'file:projects/storage-file-share.tgz':
@ -9311,7 +9358,7 @@ packages:
dev: false
name: '@rush-temp/storage-file-share'
resolution:
integrity: sha512-DJy4BcoAO/SElZYfGfL5Kdvt6A/Z0QkJ6pxZ7J0WyDFsjXEYroWnjLnNTU8T8akAMYWEVrm5j1b6eCueGUxing==
integrity: sha512-x9NFeJIsOWCLb0xAEYcH0SykV8YgLEhEwObxq49k7iej28Q75OarDUfAhW2eIOx0vt1EfhOYj0PBTiOtO4z6OA==
tarball: 'file:projects/storage-file-share.tgz'
version: 0.0.0
'file:projects/storage-queue.tgz':
@ -9371,7 +9418,7 @@ packages:
dev: false
name: '@rush-temp/storage-queue'
resolution:
integrity: sha512-c2VKjOl+42FNBg0R1HVYtsfuiHWPANnRTzigDkBVYG9U6QIFAVE2/mWfQLdAQE/fEOjKL5swQSVeEwRLjXXHmQ==
integrity: sha512-Y//E3m5VZRYZm6FlwOAcisq4hJi7z0MTqVh9ofhdg7Hc45qr31DBd8Ci8dn03poqaS3dSIGOTBObpYyfGB9Qgw==
tarball: 'file:projects/storage-queue.tgz'
version: 0.0.0
'file:projects/template.tgz':
@ -9422,7 +9469,7 @@ packages:
dev: false
name: '@rush-temp/template'
resolution:
integrity: sha512-eRzyhbFcnQZQgh8mnwyLFrZoP/wGTFoneLug+5s2AVEENNmr6+PQjEJVTg+dTlgfWG5gJP3meWTgHke3jcRt0A==
integrity: sha512-Vw/0dXj2DcQ1NX0U+d/B2W7slh9HuoExmSU5EL0/BJClWsgIjJykyQ+wmwXafNF5lV/ltMPy7Rv+XbLft7K/tw==
tarball: 'file:projects/template.tgz'
version: 0.0.0
'file:projects/test-utils-recorder.tgz':
@ -9434,8 +9481,12 @@ packages:
'@rollup/plugin-replace': 2.3.1_rollup@1.31.0
'@types/chai': 4.2.8
'@types/fs-extra': 8.0.1
'@types/md5': 2.1.33
'@types/mocha': 5.2.7
'@types/mock-fs': 4.10.0
'@types/mock-require': 2.0.0
'@types/nise': 1.4.0
'@types/node': 8.10.59
chai: 4.2.0
fs-extra: 8.1.0
karma: 4.4.1
@ -9451,9 +9502,12 @@ packages:
karma-mocha: 1.3.0
karma-mocha-reporter: 2.2.5_karma@4.4.1
karma-remap-istanbul: 0.6.0_karma@4.4.1
md5: 2.2.1
mocha: 6.2.2
mocha-junit-reporter: 1.23.3_mocha@6.2.2
mocha-multi: 1.1.3_mocha@6.2.2
mock-fs: 4.10.4
mock-require: 3.0.3
nise: 1.5.3
nock: 11.7.2
npm-run-all: 4.1.5
@ -9470,7 +9524,7 @@ packages:
dev: false
name: '@rush-temp/test-utils-recorder'
resolution:
integrity: sha512-YXDtTZfZN4hHuqameLnHfJ9tjrnUAx8Ics3yDlDp+1XOGLZ83wTDEgNj5UL5ORDtegriqrELMT0f7n88QL2Gbw==
integrity: sha512-BDMvFHxK4sXDE9cDKdYu+dNxmkpseGf/iGu+r6c4X0YBKYJ/eH9tKbOhXPi9sXjFgQeUkbPc0vRGsj+HS+/gsQ==
tarball: 'file:projects/test-utils-recorder.tgz'
version: 0.0.0
'file:projects/testhub.tgz':
@ -9491,7 +9545,7 @@ packages:
dev: false
name: '@rush-temp/testhub'
resolution:
integrity: sha512-guDU8PdEdKCVnGxNd1JEkmqukDoc1wodkEqQCWpY1+bX4ZT+ZY520gfVcMeMHYCEO8TAAhScGNke/y7p9qBArA==
integrity: sha512-dW7m2LfMTGWZVxeZCvuHSzjBQBmz1868xK4zIx5AQifXDsBDrrQ3wdPHnarQV4yI0wcyzcI0QnVzRJ42++O4Hg==
tarball: 'file:projects/testhub.tgz'
version: 0.0.0
registry: ''

Просмотреть файл

@ -1,7 +1,7 @@
// https://github.com/karma-runner/karma-chrome-launcher
process.env.CHROME_BIN = require("puppeteer").executablePath();
require("dotenv").config({ path: "../.env" });
const { jsonRecordingFilterFunction, isPlaybackMode } = require("@azure/test-utils-recorder");
const { jsonRecordingFilterFunction, isPlaybackMode, isSoftRecordMode, isRecordMode } = require("@azure/test-utils-recorder");
module.exports = function(config) {
config.set({
@ -28,7 +28,7 @@ module.exports = function(config) {
// Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.keys
"https://cdn.polyfill.io/v2/polyfill.js?features=Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.keys|always",
"dist-test/index.browser.js"
].concat(isPlaybackMode() ? ["recordings/browsers/**/*.json"] : []),
].concat((isPlaybackMode() || isSoftRecordMode()) ? ["recordings/browsers/**/*.json"] : []),
exclude: [],
@ -99,7 +99,7 @@ module.exports = function(config) {
browserDisconnectTolerance: 3,
browserConsoleLogOptions: {
// IMPORTANT: COMMENT the following line if you want to print debug logs in your browsers in record mode!!
terminal: process.env.TEST_MODE !== "record"
terminal: !isRecordMode()
},
client: {

Просмотреть файл

@ -1,7 +1,7 @@
// https://github.com/karma-runner/karma-chrome-launcher
process.env.CHROME_BIN = require("puppeteer").executablePath();
require("dotenv").config({ path: "../.env" });
const { jsonRecordingFilterFunction, isPlaybackMode } = require("@azure/test-utils-recorder");
const { jsonRecordingFilterFunction, isPlaybackMode, isSoftRecordMode, isRecordMode } = require("@azure/test-utils-recorder");
module.exports = function(config) {
config.set({
@ -28,7 +28,7 @@ module.exports = function(config) {
// Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.keys
"https://cdn.polyfill.io/v2/polyfill.js?features=Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.keys|always",
"dist-test/index.browser.js"
].concat(isPlaybackMode() ? ["recordings/browsers/**/*.json"] : []),
].concat((isPlaybackMode() || isSoftRecordMode()) ? ["recordings/browsers/**/*.json"] : []),
exclude: [],
@ -99,7 +99,7 @@ module.exports = function(config) {
browserDisconnectTolerance: 3,
browserConsoleLogOptions: {
// IMPORTANT: COMMENT the following line if you want to print debug logs in your browsers in record mode!!
terminal: process.env.TEST_MODE !== "record"
terminal: !isRecordMode()
},
client: {

Просмотреть файл

@ -1,7 +1,7 @@
// https://github.com/karma-runner/karma-chrome-launcher
process.env.CHROME_BIN = require("puppeteer").executablePath();
require("dotenv").config({ path: "../.env" });
const { jsonRecordingFilterFunction, isPlaybackMode } = require("@azure/test-utils-recorder");
const { jsonRecordingFilterFunction, isPlaybackMode, isSoftRecordMode, isRecordMode } = require("@azure/test-utils-recorder");
module.exports = function(config) {
config.set({
@ -28,7 +28,7 @@ module.exports = function(config) {
// Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.keys
"https://cdn.polyfill.io/v2/polyfill.js?features=Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.keys|always",
"dist-test/index.browser.js"
].concat(isPlaybackMode() ? ["recordings/browsers/**/*.json"] : []),
].concat((isPlaybackMode() || isSoftRecordMode()) ? ["recordings/browsers/**/*.json"] : []),
exclude: [],
@ -99,7 +99,7 @@ module.exports = function(config) {
browserDisconnectTolerance: 3,
browserConsoleLogOptions: {
// IMPORTANT: COMMENT the following line if you want to print debug logs in your browsers in record mode!!
terminal: process.env.TEST_MODE !== "record"
terminal: !isRecordMode()
},
client: {

Просмотреть файл

@ -1,7 +1,7 @@
// https://github.com/karma-runner/karma-chrome-launcher
process.env.CHROME_BIN = require("puppeteer").executablePath();
require("dotenv").config({ path: "../.env" });
const { jsonRecordingFilterFunction, isPlaybackMode } = require("@azure/test-utils-recorder");
const { jsonRecordingFilterFunction, isPlaybackMode, isSoftRecordMode, isRecordMode } = require("@azure/test-utils-recorder");
module.exports = function(config) {
config.set({
@ -33,7 +33,7 @@ module.exports = function(config) {
// Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.assign,Object.keys
"https://cdn.polyfill.io/v2/polyfill.js?features=Symbol,Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.assign,Object.keys|always",
"dist-test/index.browser.js"
].concat(isPlaybackMode() ? ["recordings/browsers/**/*.json"] : []),
].concat((isPlaybackMode() || isSoftRecordMode()) ? ["recordings/browsers/**/*.json"] : []),
// list of files / patterns to exclude
exclude: [],
@ -119,7 +119,7 @@ module.exports = function(config) {
browserDisconnectTimeout: 10000,
browserDisconnectTolerance: 3,
browserConsoleLogOptions: {
terminal: process.env.TEST_MODE !== "record"
terminal: !isRecordMode()
},
client: {

Просмотреть файл

@ -1,7 +1,7 @@
// https://github.com/karma-runner/karma-chrome-launcher
process.env.CHROME_BIN = require("puppeteer").executablePath();
require("dotenv").config({ path: "../.env" });
const { jsonRecordingFilterFunction, isPlaybackMode } = require("@azure/test-utils-recorder");
const { jsonRecordingFilterFunction, isPlaybackMode, isSoftRecordMode, isRecordMode } = require("@azure/test-utils-recorder");
module.exports = function(config) {
config.set({
@ -33,7 +33,7 @@ module.exports = function(config) {
// Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.assign,Object.keys
"https://cdn.polyfill.io/v2/polyfill.js?features=Symbol,Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.assign,Object.keys|always",
"dist-test/index.browser.js"
].concat(isPlaybackMode() ? ["recordings/browsers/**/*.json"] : []),
].concat((isPlaybackMode() || isSoftRecordMode()) ? ["recordings/browsers/**/*.json"] : []),
// list of files / patterns to exclude
exclude: [],
@ -118,7 +118,7 @@ module.exports = function(config) {
browserDisconnectTimeout: 10000,
browserDisconnectTolerance: 3,
browserConsoleLogOptions: {
terminal: process.env.TEST_MODE !== "record"
terminal: !isRecordMode()
},
client: {

Просмотреть файл

@ -1,7 +1,7 @@
// https://github.com/karma-runner/karma-chrome-launcher
process.env.CHROME_BIN = require("puppeteer").executablePath();
require("dotenv").config({ path: "../.env" });
const { jsonRecordingFilterFunction, isPlaybackMode } = require("@azure/test-utils-recorder");
const { jsonRecordingFilterFunction, isPlaybackMode, isSoftRecordMode, isRecordMode } = require("@azure/test-utils-recorder");
module.exports = function(config) {
config.set({
@ -33,7 +33,7 @@ module.exports = function(config) {
// Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.assign,Object.keys
"https://cdn.polyfill.io/v2/polyfill.js?features=Symbol,Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.assign,Object.keys|always",
"dist-test/index.browser.js"
].concat(isPlaybackMode() ? ["recordings/browsers/**/*.json"] : []),
].concat((isPlaybackMode() || isSoftRecordMode()) ? ["recordings/browsers/**/*.json"] : []),
// list of files / patterns to exclude
exclude: [],
@ -119,7 +119,7 @@ module.exports = function(config) {
browserDisconnectTimeout: 10000,
browserDisconnectTolerance: 3,
browserConsoleLogOptions: {
terminal: process.env.TEST_MODE !== "record"
terminal: !isRecordMode()
},
client: {

Просмотреть файл

@ -1,7 +1,7 @@
// https://github.com/karma-runner/karma-chrome-launcher
process.env.CHROME_BIN = require("puppeteer").executablePath();
require("dotenv").config({ path: "../.env" });
const { jsonRecordingFilterFunction, isPlaybackMode } = require("@azure/test-utils-recorder");
const { jsonRecordingFilterFunction, isPlaybackMode, isSoftRecordMode, isRecordMode } = require("@azure/test-utils-recorder");
module.exports = function(config) {
config.set({
@ -33,7 +33,7 @@ module.exports = function(config) {
// Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.assign,Object.keys
"https://cdn.polyfill.io/v2/polyfill.js?features=Symbol,Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.assign,Object.keys|always",
"dist-test/index.browser.js"
].concat(isPlaybackMode() ? ["recordings/browsers/**/*.json"] : []),
].concat((isPlaybackMode() || isSoftRecordMode()) ? ["recordings/browsers/**/*.json"] : []),
// list of files / patterns to exclude
exclude: [],
@ -119,7 +119,7 @@ module.exports = function(config) {
browserDisconnectTimeout: 10000,
browserDisconnectTolerance: 3,
browserConsoleLogOptions: {
terminal: process.env.TEST_MODE !== "record"
terminal: !isRecordMode()
},
client: {

Просмотреть файл

@ -1,8 +1,9 @@
## 2020-02-06
- If any URLs are meant to be replaced in the recordings with `replaceableVariables`, hostname from the URLs will be independently matched and replaced. [#7204](https://github.com/Azure/azure-sdk-for-js/issues/7204)
- Example - ENV_VAR `ACCOUNT_URL=https://azureaccount.com/` is supposed to be replaced with `https://endpoint/`, `azureaccount.com` will be independently matched and replaced with `endpoint` too.
- Added the "soft-record" mode, which allows users to only record the tests that have changed. [#7213](https://github.com/Azure/azure-sdk-for-js/issues/7213)
- [BUG FIX] Tests leveraging `coreHttp.requestOptions.timeout` with empty recordings(no nock scopes with request-responses defined in the recording) fail during the playback mode when executed along with other tests. Fixed the issue by resetting nock's global state. More info - [#7264](https://github.com/Azure/azure-sdk-for-js/issues/7264)

Просмотреть файл

@ -8,6 +8,12 @@
- Tests hit the live-service
- [Nock](https://www.npmjs.com/package/nock)/[Nise](https://www.npmjs.com/package/nise) are leveraged for recording the request-responses for future use
- If recordings are already present, forces re-recording
- The recorded files contain a hash of the test that executed that recording.
- Some recorded files might not have the hash if they were generated by an older version of the recorder.
- If TEST_MODE = "soft-record",
- Tests will be skipped if they haven't changed. `soft-record` mode is isomorphous to `record` mode in all other aspects.
- We determine whether a test has changed or not by doing a MD5 hash of the test's source code.
- If the hash is not stored in the recordings, they will be re-recorded to include the hash.
- Else If TEST_MODE = "live",
- Tests hit the live-service, we don't record the requests/responses
- Else If TEST_MODE = "playback" (or if the TEST_MODE is neither "record" nor "live"),

Просмотреть файл

@ -142,9 +142,9 @@ import { record, env, delay } from "@azure/test-utils-recorder";
The common recorder provides the following public methods and properties:
- `record`: Which deals with recording and playing back the network requests,
- `record()`: Which deals with recording and playing back the network requests,
depending on the value assigned to the `TEST_MODE` environment variable. If
`TEST_MODE` equals to `record`, it will automatically store network requests
`TEST_MODE` equals to `record` or `soft-record`, it will automatically store network requests
in a plain text file in the folder `recordings` at the root of your
repository (which for our example case is the root of the
`@azure/keyvault/keyvault-keys` repository).
@ -155,7 +155,7 @@ The common recorder provides the following public methods and properties:
package in the repo. It also returns an object with a method `stop()`, which
will allow you to control when you want the recorder to stop re-routing your
http requests.
- `Recorder`: The return of `record` is going to be an instance of the
- `Recorder`: The return of the `record()` method is going to be an instance of the
`Recorder`, which has some useful functions: `stop`, `skip`, `getUniqueName`,
and `newDate`. `stop` will stop the recorder from storing a copy of the HTTP
requests and responses. `skip` will pause the recorder only during the test
@ -169,7 +169,9 @@ The common recorder provides the following public methods and properties:
but only while the `TEST_MODE` is not `playback`, since you want to make sure you can run the playback tests
as fast as possible.
- `isRecordMode`, which is a shorthand for checking if the environment variable
`TEST_MODE` is set to `record`.
`TEST_MODE` is set to `record` or `soft-record`.
- `isSoftRecordMode`, which is a shorthand for checking if the environment
variable `TEST_MODE` is set to `soft-record` only.
- `isPlaybackMode`, which is a shorthand for checking if the environment
variable `TEST_MODE` is set to `playback`.
- `setReplaceableVariables`, which will allow you to hide sensitive content
@ -183,12 +185,12 @@ The common recorder provides the following public methods and properties:
### Configuring your project
Having the common recorder as a devDependency means that you'll be able to start
recording tests right away by using the exported method `record`. We'll get
recording tests right away by using the exported method `record()`. We'll get
into the details further down this document. This function will do recordings,
or will play back previous recordings, depending on an environment variable:
`TEST_MODE`. If the environment variable `TEST_MODE` is empty, `record` (and most
`TEST_MODE`. If the environment variable `TEST_MODE` is empty, `record()` (and most
of the functions provided by test-utils-recorder) won't be doing anything. You'll need
to set this environment variable to `record` to start recording, and then to
to set the `TEST_MODE` environment variable to `record` (or `soft-record`) to start recording, and then to
`playback` to play the recordings back at your code.
#### package.json scripts
@ -297,7 +299,7 @@ section of our guidelines:
### How to record
To record your tests, make sure to set the environment variable `TEST_MODE` to
`record`, then in your code, call to the `record` function exported from
`record` or `soft-record`, then in your code, call to the `record()` function exported from
`@azure/test-utils-recorder`, then call it before the http request you want to
make. In the following example, we'll invoke the `record()` method before
authenticating our KeyVault client:
@ -336,6 +338,9 @@ After running this test with the `TEST_MODE` environment variable set to
`recordings/node/my_test/recording_before_each_hook.js` with the contents of
the HTTP request as well as the contents of the HTTP response.
If `TEST_MODE` is set to `soft-record` instead, the recorder will only create
this recording file if the test has changed from a previous execution.
You'll see in the code above that we're invoking `recorder.stop`. This is so
that, after each test, we can stop recording and the test file can be
generated. We recommend creating new recorders on `beforeEach` and stopping the
@ -391,6 +396,8 @@ request according to their matching copy stored in the recordings.
Once you have your recorded files, to update them after changing one of the tests, simply
re-run the tests with `TEST_MODE` set to `record`. This will overwrite previously existing files.
Or re run the tests with `TEST_MODE` set to `soft-record` to only overwrite the files related to
tests that have changed.
> **Note:** If you rename the file of the test, or the name of the test, the
> path of the recording will change. Make sure to delete the recordings
@ -482,7 +489,7 @@ Some HTTP requests might have parameters with sensitive information. To get
them out of your recordings, you can call to `skipQueryParams` with an array of strings
where you specify the names of the query parameter you want to remove.
For example, give nthat we find this query parameters in our recordings:
For example, given that we find this query parameters in our recordings:
`?sv=2018-11-09&sr=c&sig=<sig>&sktid=<sktid>&skv=2018-11-09&se=2019-08-07T07%3A00%3A00Z&sp=rwdl`,
if we don't want the parameters "sr", "sig" and "sp" to appear in these files, we can do the following:

Просмотреть файл

@ -96,7 +96,15 @@ module.exports = function(config) {
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
// 'ChromeHeadless', 'Chrome', 'Firefox', 'Edge', 'IE'
browsers: ["ChromeHeadless"],
// --no-sandbox allows our tests to run in Linux without having to change the system.
// --disable-web-security allows us to authenticate from the browser without having to write tests using interactive auth, which would be far more complex.
browsers: ["ChromeHeadlessNoSandbox"],
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: "ChromeHeadless",
flags: ["--no-sandbox", "--disable-web-security"]
}
},
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
@ -110,7 +118,8 @@ module.exports = function(config) {
browserDisconnectTimeout: 10000,
browserDisconnectTolerance: 3,
browserConsoleLogOptions: {
terminal: process.env.TEST_MODE !== "record"
// We would usually hide the logs from the tests, but we don't need to do this inside of the recorder package because we are not recording the tests.
// // terminal: process.env.TEST_MODE !== "record"
},
client: {

Просмотреть файл

@ -71,9 +71,13 @@
"@rollup/plugin-node-resolve": "^7.0.0",
"@rollup/plugin-replace": "^2.2.0",
"@types/fs-extra": "^8.0.0",
"@types/chai": "^4.1.6",
"@types/md5": "~2.1.33",
"@types/mocha": "^5.2.5",
"@types/nise": "^1.4.0",
"@types/chai": "^4.1.6",
"@types/node": "^8.0.0",
"@types/mock-require": "~2.0.0",
"@types/mock-fs": "~4.10.0",
"chai": "^4.2.0",
"karma": "^4.0.1",
"karma-chrome-launcher": "^3.0.0",
@ -88,9 +92,12 @@
"karma-mocha": "^1.3.0",
"karma-mocha-reporter": "^2.2.5",
"karma-remap-istanbul": "^0.6.0",
"md5": "^2.2.1",
"mocha": "^6.2.2",
"mocha-junit-reporter": "^1.18.0",
"mocha-multi": "^1.1.3",
"mock-fs": "^4.10.4",
"mock-require": "^3.0.3",
"npm-run-all": "^4.1.5",
"nyc": "^14.0.0",
"prettier": "^1.16.4",

Просмотреть файл

@ -10,10 +10,11 @@ import {
parseUrl,
isPlaybackMode,
isRecordMode,
findRecordingsFolderPath,
RecorderEnvironmentSetup,
filterSecretsFromStrings,
filterSecretsRecursivelyFromJSON
filterSecretsRecursivelyFromJSON,
generateTestRecordingFilePath,
nodeRequireRecordingIfExists
} from "./utils";
import { customConsoleLog } from "./customConsoleLog";
@ -60,33 +61,20 @@ export abstract class BaseRecorder {
customizationsOnRecordings: [],
queryParametersToSkip: []
};
protected hash: string;
constructor(platform: "node" | "browsers", testSuiteTitle: string, testTitle: string) {
// File Extension
// nock recordings for node tests - .js extension
// recordings are saved in json format for browser tests - .json extension
const ext = platform === "node" ? "js" : "json";
// Filepath - `{node|browsers}/<describe-block-title>/recording_<test-title>.{js|json}`
this.relativeTestRecordingFilePath =
platform +
"/" +
this.formatPath(testSuiteTitle) +
"/recording_" +
this.formatPath(testTitle) +
"." +
ext;
}
protected formatPath(path: string): string {
return path
.toLowerCase()
.replace(/ /g, "_")
.replace(/<=/g, "lte")
.replace(/>=/g, "gte")
.replace(/</g, "lt")
.replace(/>/g, "gt")
.replace(/=/g, "eq")
.replace(/\W/g, "");
constructor(
platform: "node" | "browsers",
hash: string,
testSuiteTitle: string,
testTitle: string
) {
this.hash = hash;
this.relativeTestRecordingFilePath = generateTestRecordingFilePath(
platform,
testSuiteTitle,
testTitle
);
}
/**
@ -122,8 +110,8 @@ export abstract class BaseRecorder {
}
export class NockRecorder extends BaseRecorder {
constructor(testSuiteTitle: string, testTitle: string) {
super("node", testSuiteTitle, testTitle);
constructor(hash: string, testSuiteTitle: string, testTitle: string) {
super("node", hash, testSuiteTitle, testTitle);
}
public record(recorderEnvironmentSetup: RecorderEnvironmentSetup): void {
@ -133,7 +121,7 @@ export class NockRecorder extends BaseRecorder {
});
}
public playback(recorderEnvironmentSetup: RecorderEnvironmentSetup, filePath: string): void {
public playback(recorderEnvironmentSetup: RecorderEnvironmentSetup, testFilePath: string): void {
this.environmentSetup = recorderEnvironmentSetup;
/**
* `@azure/test-utils-recorder` package is used for both the browser and node tests
@ -142,19 +130,12 @@ export class NockRecorder extends BaseRecorder {
* `path` module is leveraged to import the node test recordings and `path` module can't be imported in the browser.
* So, instead of `import`-ing the `path` library, `require` is being used and this code path is never executed in the browser.
*
* [A diiferent strategy is in place to import recordings for browser tests by leveraging `karma` plugins.]
* [A different strategy is in place to import recordings for browser tests by leveraging `karma` plugins.]
*/
let path = require("path");
// Get the full path of the `recordings` folder by navigating through the hierarchy of the test file path.
const recordingsFolderPath = findRecordingsFolderPath(filePath);
const recordingPath = path.resolve(recordingsFolderPath, this.relativeTestRecordingFilePath);
if (fs.existsSync(recordingPath)) {
this.uniqueTestInfo = require(recordingPath).testInfo;
} else {
throw new Error(
`Recording (${this.relativeTestRecordingFilePath}) is not found at ${recordingsFolderPath}`
);
}
this.uniqueTestInfo = nodeRequireRecordingIfExists(
this.relativeTestRecordingFilePath,
testFilePath
).testInfo;
}
public stop(): void {
@ -163,6 +144,8 @@ export class NockRecorder extends BaseRecorder {
const importNockStatement =
"let nock = require('nock');\n" +
"\n" +
`module.exports.hash = "${this.hash}";\n` +
"\n" +
"module.exports.testInfo = " +
JSON.stringify(this.uniqueTestInfo) +
"\n";
@ -232,8 +215,8 @@ export class NockRecorder extends BaseRecorder {
export class NiseRecorder extends BaseRecorder {
private recordings: any[] = [];
constructor(testSuiteTitle: string, testTitle: string) {
super("browsers", testSuiteTitle, testTitle);
constructor(hash: string, testSuiteTitle: string, testTitle: string) {
super("browsers", hash, testSuiteTitle, testTitle);
}
// Inserts a request/response pair into the recordings array
@ -308,7 +291,7 @@ export class NiseRecorder extends BaseRecorder {
// 'onCreate' function is called when a new fake XMLHttpRequest object (req) is created
// Our intent is to override the request's 'onreadystatechange' function so we can create a recording once the response is ready
// We can only override 'onreadystatechange' AFTER the 'send' function is called because we need to make sure our implementation won't be overriden by the client
// We can only override 'onreadystatechange' AFTER the 'send' function is called because we need to make sure our implementation won't be overridden by the client
// But we can only override 'send' AFTER the 'open' function is called because the filter we set above makes Nise override it in 'open' body
xhr.onCreate = function(req: any) {
// We'll override the 'open' function, so we need to store a handle to its original implementation
@ -339,7 +322,7 @@ export class NiseRecorder extends BaseRecorder {
}
};
// Now that we have overriden 'onreadystatechange', we can send the request to the server
// Now that we have overridden 'onreadystatechange', we can send the request to the server
reqSend.apply(req, arguments);
};
};
@ -424,7 +407,8 @@ export class NiseRecorder extends BaseRecorder {
path: "./recordings/" + this.relativeTestRecordingFilePath,
content: {
recordings: this.recordings,
uniqueTestInfo: this.uniqueTestInfo
uniqueTestInfo: this.uniqueTestInfo,
hash: this.hash
}
})
);

Просмотреть файл

@ -8,6 +8,7 @@ export {
isPlaybackMode,
isRecordMode,
isLiveMode,
isSoftRecordMode,
RecorderEnvironmentSetup
} from "./utils";
export { jsonRecordingFilterFunction } from "./basekarma.conf";

Просмотреть файл

@ -7,7 +7,9 @@ import {
isRecordMode,
isPlaybackMode,
RecorderEnvironmentSetup,
env
env,
isSoftRecordMode,
testHasChanged
} from "./utils";
import {
NiseRecorder,
@ -16,6 +18,7 @@ import {
setEnvironmentOnLoad,
setEnvironmentVariables
} from "./baseRecorder";
import MD5 from "md5";
/**
* @export
@ -28,7 +31,7 @@ export interface Recorder {
*/
stop(): void;
/**
* `{recorder.skip("node")}` and `{recorder.skip("browser")}` will skip the test in node.js and browser runtimes repectively.
* `{recorder.skip("node")}` and `{recorder.skip("browser")}` will skip the test in node.js and browser runtimes respectively.
* If the `{runtime}` is `{undefined}`, the test will be skipped in both the node and browser runtimes.
* Has no effect in the live test mode.
*/
@ -88,12 +91,26 @@ export function record(
testTitle = testContext.test!.title;
}
const stringTest = testContext.currentTest!.fn!.toString();
const currentHash = MD5(stringTest);
const testAbsolutePath = testContext.currentTest!.file!;
if (
isSoftRecordMode() &&
!testHasChanged(testHierarchy, testTitle, testAbsolutePath, currentHash)
) {
testContext.test!.title = `${
testContext.test!.title
} (Test unchanged since last recording)`;
testContext.skip();
}
setEnvironmentOnLoad();
if (isBrowser()) {
recorder = new NiseRecorder(testHierarchy, testTitle);
recorder = new NiseRecorder(currentHash, testHierarchy, testTitle);
} else {
recorder = new NockRecorder(testHierarchy, testTitle);
recorder = new NockRecorder(currentHash, testHierarchy, testTitle);
}
if (isRecordMode()) {
@ -103,18 +120,21 @@ export function record(
} else if (isPlaybackMode()) {
// If TEST_MODE=playback,
// 1. sets up the ENV variables
// 2. invokes the recorder, play the exisiting test recording.
// 2. invokes the recorder, play the existing test recording.
setEnvironmentVariables(env, recorderEnvironmentSetup.replaceableVariables);
recorder.playback(recorderEnvironmentSetup, testContext.currentTest!.file!);
recorder.playback(recorderEnvironmentSetup, testAbsolutePath);
}
// If TEST_MODE=live, hits the live-service and no recordings are generated.
return {
stop: function() {
recorder.stop();
// We check wether we're on record or playback inside of the recorder's stop method.
if (recorder) {
recorder.stop();
}
},
/**
* `{recorder.skip("node")}` and `{recorder.skip("browser")}` will skip the test in node.js and browser runtimes repectively.
* `{recorder.skip("node")}` and `{recorder.skip("browser")}` will skip the test in node.js and browser runtimes respectively.
* `{recorder.skip()}` If the `{runtime}` is undefined, the test will be skipped in both the node and browser runtimes.
* @param runtime Can either be `"node"` or `"browser"` or `undefined`
* @param reason Reason for skipping the test

Просмотреть файл

@ -54,7 +54,13 @@ export interface RecorderEnvironmentSetup {
export const env = isBrowser() ? (window as any).__env__ : process.env;
export function isRecordMode() {
return env.TEST_MODE === "record";
// It should be safe to assume that these two can be considered being in record mode.
// For more specific distinctions, one can use isSoftRecordMode.
return env.TEST_MODE === "record" || isSoftRecordMode();
}
export function isSoftRecordMode() {
return env.TEST_MODE === "soft-record";
}
export function isLiveMode() {
@ -369,3 +375,85 @@ export function findRecordingsFolderPath(filePath: string): string {
);
}
}
export function formatPath(path: string): string {
return path
.toLowerCase()
.replace(/ /g, "_")
.replace(/<=/g, "lte")
.replace(/>=/g, "gte")
.replace(/</g, "lt")
.replace(/>/g, "gt")
.replace(/=/g, "eq")
.replace(/\W/g, "");
}
/**
* Generates a file path with the following structure:
*
* `{node|browsers}/<describe-block-title>/recording_<test-title>.{js|json}`
*
* @param platform A string, either "node" or "browsers".
* @param testSuiteTitle The title of the test suite.
* @param testTitle The title of the specific test we're running.
*/
export function generateTestRecordingFilePath(
platform: "node" | "browsers",
testSuiteTitle: string,
testTitle: string
): string {
// File Extension
// nock recordings for node tests - .js extension
// recordings are saved in json format for browser tests - .json extension
const ext = platform === "node" ? "js" : "json";
return `${platform}/${formatPath(testSuiteTitle)}/recording_${formatPath(testTitle)}.${ext}`;
}
/**
* Requires a file if it exists. Only works on NodeJS.
*/
export function nodeRequireRecordingIfExists(recordingPath: string, testAbsolutePath: string): any {
if (isBrowser()) throw new Error("nodeRequireRecordingIfExists only works on NodeJS");
const path = require("path");
// Get the full path of the `recordings` folder by navigating through the hierarchy of the test file path.
const recordingsFolderPath = findRecordingsFolderPath(testAbsolutePath);
const absoluteRecordingPath = path.resolve(recordingsFolderPath, recordingPath);
if (fs.existsSync(absoluteRecordingPath)) {
return require(absoluteRecordingPath);
} else {
throw new Error(`The recording ${recordingPath} was not found in ${recordingsFolderPath}`);
}
}
/**
* Checks if a test hasn't changed from the last time it was recorded.
* @param testContext
* @param testSuiteTitle
* @param testTitle
* @param currentHash
*/
export function testHasChanged(
testSuiteTitle: string,
testTitle: string,
testAbsolutePath: string,
currentHash: string
): boolean {
const platform = isBrowser() ? "browsers" : "node";
const recordingPath: string = generateTestRecordingFilePath(platform, testSuiteTitle, testTitle);
let previousHash: string = "";
if (platform === "node") {
try {
previousHash = nodeRequireRecordingIfExists(recordingPath, testAbsolutePath).hash;
} catch (e) {}
} else if ((window as any).__json__["recordings/" + recordingPath]) {
previousHash = (window as any).__json__["recordings/" + recordingPath].hash;
}
if (!previousHash) {
return true;
}
return previousHash !== currentHash;
}

Просмотреть файл

@ -0,0 +1,39 @@
import { testHasChanged, generateTestRecordingFilePath } from "../../src/utils";
import chai from "chai";
const { expect } = chai;
describe("Browser utils", () => {
describe("testHasChanged", () => {
it("Should not crash if the recorded file doesn't exist", function() {
const testSuiteTitle = this.test!.parent!.fullTitle();
const testTitle = this.test!.title;
(window as any).__json__ = {};
// We won't be testing whether MD5 works or not.
const newHash = "new hash";
expect(testHasChanged(testSuiteTitle, testTitle, "test/myTest.spec.ts", newHash)).to.equal(
true
);
});
it("Should return true if the older hash doesn't exist", function() {
const platform = "browsers";
const testSuiteTitle = this.test!.parent!.fullTitle();
const testTitle = this.test!.title;
const filePath = generateTestRecordingFilePath(platform, testSuiteTitle, testTitle);
(window as any).__json__ = {
["recordings/" + filePath]: {}
};
// We won't be testing whether MD5 works or not.
const newHash = "new hash";
expect(testHasChanged(testSuiteTitle, testTitle, "test/myTest.spec.ts", newHash)).to.equal(
true
);
});
});
});

Просмотреть файл

@ -0,0 +1,204 @@
import {
generateTestRecordingFilePath,
nodeRequireRecordingIfExists,
isBrowser,
findRecordingsFolderPath,
testHasChanged
} from "../../src/utils";
import chai from "chai";
const { expect } = chai;
describe("NodeJS utils", () => {
describe("nodeRequireRecordingIfExists", () => {
it("should be able to load the contents of a recording file if the file exists", function() {
const mockFs = require("mock-fs");
const mockRequire = require("mock-require");
// This needs to change if findRecordingsFolderPath changes.
mockFs({
// Our lazy require doesn't use the fs module internally.
"recordings/recording.json": "",
"test/myTest.spec.ts": "",
"../../sdk/": {},
"../../../rush.json": ""
});
mockRequire("../recordings/recording.json", {
property: "value"
});
const path = require("path");
const testAbsolutePath = path.resolve("test/myTest.spec.ts");
expect(nodeRequireRecordingIfExists("recording.json", testAbsolutePath).property).to.equal(
"value"
);
mockFs.restore();
mockRequire.stopAll();
});
it("should throw if the file at a given recording path doesn't exist", function() {
if (isBrowser()) return this.skip();
// Require shouldn't be mocked in this test since we should be preventing require from being reached.
const mockFs = require("mock-fs");
// This needs to change if findRecordingsFolderPath changes.
mockFs({
recordings: {},
"test/myTest.spec.ts": "",
"../../sdk/": {},
"../../../rush.json": ""
});
const path = require("path");
const testAbsolutePath = path.resolve("test/myTest.spec.ts");
let error: Error | undefined;
try {
nodeRequireRecordingIfExists("recording.json", testAbsolutePath);
} catch (e) {
error = e;
}
expect(error!.message).to.equal(
`The recording recording.json was not found in ${findRecordingsFolderPath(
"recording.json"
)}`
);
mockFs.restore();
});
});
describe("testHasChanged", () => {
it("should not crash if the recorded file doesn't exist", function() {
const mockFs = require("mock-fs");
const mockRequire = require("mock-require");
const testSuiteTitle = this.test!.parent!.fullTitle();
const testTitle = this.test!.title;
// This needs to change if findRecordingsFolderPath changes.
mockFs({
// Our lazy require doesn't use the fs module internally.
recordings: {},
"test/myTest.spec.ts": "",
"../../sdk/": {},
"../../../rush.json": ""
});
// We won't be testing whether MD5 works or not.
const newHash = "new hash";
const path = require("path");
const testAbsolutePath = path.resolve("test/myTest.spec.ts");
expect(testHasChanged(testSuiteTitle, testTitle, testAbsolutePath, newHash)).to.equal(true);
mockFs.restore();
mockRequire.stopAll();
});
it("should return true if the older hash doesn't exist", function() {
const mockFs = require("mock-fs");
const mockRequire = require("mock-require");
const platform = "node";
const testSuiteTitle = this.test!.parent!.fullTitle();
const testTitle = this.test!.title;
const filePath = generateTestRecordingFilePath(platform, testSuiteTitle, testTitle);
// This needs to change if findRecordingsFolderPath changes.
mockFs({
// Our lazy require doesn't use the fs module internally.
[`recordings/${filePath}`]: "",
"test/myTest.spec.ts": "",
"../../sdk/": {},
"../../../rush.json": ""
});
mockRequire(`../recordings/${filePath}`, {});
// We won't be testing whether MD5 works or not.
const newHash = "new hash";
const path = require("path");
const testAbsolutePath = path.resolve("test/myTest.spec.ts");
expect(testHasChanged(testSuiteTitle, testTitle, testAbsolutePath, newHash)).to.equal(true);
mockFs.restore();
mockRequire.stopAll();
});
it("should return false if the older hash is the same as the new hash", function() {
const mockFs = require("mock-fs");
const mockRequire = require("mock-require");
const platform = "node";
const testSuiteTitle = this.test!.parent!.fullTitle();
const testTitle = this.test!.title;
const filePath = generateTestRecordingFilePath(platform, testSuiteTitle, testTitle);
// This needs to change if findRecordingsFolderPath changes.
mockFs({
// Our lazy require doesn't use the fs module internally.
[`recordings/${filePath}`]: "",
"test/myTest.spec.ts": "",
"../../sdk/": {},
"../../../rush.json": ""
});
mockRequire(`../recordings/${filePath}`, {
// We won't be testing whether MD5 works or not.
hash: "same old hash"
});
// We won't be testing whether MD5 works or not.
const newHash = "same old hash";
const path = require("path");
const testAbsolutePath = path.resolve("test/myTest.spec.ts");
expect(testHasChanged(testSuiteTitle, testTitle, testAbsolutePath, newHash)).to.equal(false);
mockFs.restore();
mockRequire.stopAll();
});
it("should return true if the older hash is different than the new hash", function() {
const mockFs = require("mock-fs");
const mockRequire = require("mock-require");
const platform = "node";
const testSuiteTitle = this.test!.parent!.fullTitle();
const testTitle = this.test!.title;
const filePath = generateTestRecordingFilePath(platform, testSuiteTitle, testTitle);
// This needs to change if findRecordingsFolderPath changes.
mockFs({
// Our lazy require doesn't use the fs module internally.
[`recordings/${filePath}`]: "",
"test/myTest.spec.ts": "",
"../../sdk/": {},
"../../../rush.json": ""
});
mockRequire(`../recordings/${filePath}`, {
// We won't be testing whether MD5 works or not.
hash: "old hash"
});
// We won't be testing whether MD5 works or not.
const newHash = "new hash";
const path = require("path");
const testAbsolutePath = path.resolve("test/myTest.spec.ts");
expect(testHasChanged(testSuiteTitle, testTitle, testAbsolutePath, newHash)).to.equal(true);
mockFs.restore();
mockRequire.stopAll();
});
});
});

Просмотреть файл

@ -5,7 +5,8 @@ import {
encodeRFC3986,
filterSecretsFromStrings,
env,
filterSecretsRecursivelyFromJSON
filterSecretsRecursivelyFromJSON,
generateTestRecordingFilePath
} from "../src/utils";
import chai from "chai";
import { setEnvironmentVariables } from "../src/baseRecorder";
@ -47,7 +48,7 @@ describe("utils", () => {
});
describe("applyReplacementMap", () => {
it("should filter URI encoded secrets", () => {
it("Should filter URI encoded secrets", () => {
const env: NodeJS.ProcessEnv = {
SECRET: "(SECRET)"
};
@ -61,7 +62,7 @@ describe("utils", () => {
expect(appliedMap).to.equal("azure.com/url/HIDDEN_SECRET");
});
it("should filter hostname of the plain URI", () => {
it("Should filter hostname of the plain URI", () => {
const env: NodeJS.ProcessEnv = {
ENDPOINT: "https://azureaccount.net/"
};
@ -75,7 +76,7 @@ describe("utils", () => {
expect(appliedMap).to.equal("https://endpoint/");
});
it("should filter hostname of the URI irrespective of `/` at the end", () => {
it("Should filter hostname of the URI irrespective of `/` at the end", () => {
const env: NodeJS.ProcessEnv = {
ENDPOINT: "https://azureaccount.net/"
};
@ -89,7 +90,7 @@ describe("utils", () => {
expect(appliedMap).to.equal("https://endpoint");
});
it("should filter hostname of the URI irrespective of the content succeeding the hostname", () => {
it("Should filter hostname of the URI irrespective of the content succeeding the hostname", () => {
const env: NodeJS.ProcessEnv = {
ENDPOINT: "https://azureaccount.net/queue/"
};
@ -103,7 +104,7 @@ describe("utils", () => {
expect(appliedMap).to.equal("https://endpoint");
});
it("should filter raw secrets", () => {
it("Should filter raw secrets", () => {
const env: NodeJS.ProcessEnv = {
ENDPOINT: "azure.com/url/"
};
@ -117,7 +118,7 @@ describe("utils", () => {
expect(appliedMap).to.equal("default.com/path/%28SECRET%29");
});
it("should filter both, raw and URI encoded secrets", () => {
it("Should filter both, raw and URI encoded secrets", () => {
const env: NodeJS.ProcessEnv = {
SECRET: "(SECRET)",
ENDPOINT: "azure.com/url/"
@ -133,7 +134,7 @@ describe("utils", () => {
expect(appliedMap).to.equal("default.com/path/HIDDEN_SECRET");
});
it("should work with recordings of several lines", () => {
it("Should work with recordings of several lines", () => {
const env: NodeJS.ProcessEnv = {
SECRET: "(SECRET)",
ENDPOINT: "azure.com/url/"
@ -165,7 +166,7 @@ ultramarine.com/url/PUBLIC
});
describe("applyReplacementFunctions", () => {
it("should apply one replacement function", () => {
it("Should apply one replacement function", () => {
const replacements: Array<(content: string) => string> = [
(source: string): string => {
return source.replace(/banana/i, "Bonobo's");
@ -176,7 +177,7 @@ ultramarine.com/url/PUBLIC
expect(appliedFunctions).to.equal("Bonobo's Split");
});
it("should apply several replacement functions", () => {
it("Should apply several replacement functions", () => {
const replacements: Array<(content: string) => string> = [
(source: string): string => {
return source.replace(/banana/i, "Bonobo's");
@ -190,7 +191,7 @@ ultramarine.com/url/PUBLIC
expect(appliedFunctions).to.equal("Bonobo's Flex");
});
it("should work with recordings of several lines", () => {
it("Should work with recordings of several lines", () => {
const replacements = [
(source: string): string => {
return source.replace(/azure.com/g, "default.com");
@ -234,7 +235,7 @@ ultramarine.com/url/PUBLIC
expect(updatedRecording).to.deep.equal(expectedFilteredOutput);
}
it("should work for strings", () => {
it("Should work for strings", () => {
env.SECRET = "SECRET";
const replaceableVariables = { SECRET: "FAKE_IT" };
@ -243,7 +244,7 @@ ultramarine.com/url/PUBLIC
expect(updatedRecording).to.equal("HERE_IS_THE_FLAG-FAKE_IT");
});
it("should work for JSON content #1 - secret is present in the query attributes, part of the xml response string", () => {
it("Should work for JSON content #1 - secret is present in the query attributes, part of the xml response string", () => {
env.ACCOUNT_NAME = "azureaccount";
const replaceableVariables = { ACCOUNT_NAME: "fakestorageaccount" };
verifyFilterFunctionForJson(
@ -286,7 +287,7 @@ ultramarine.com/url/PUBLIC
);
});
it("should work for JSON content #2 - secret is present as part of a JSON lookalike response string ", () => {
it("Should work for JSON content #2 - secret is present as part of a JSON lookalike response string ", () => {
verifyFilterFunctionForJson(
{
recording: [
@ -312,7 +313,7 @@ ultramarine.com/url/PUBLIC
);
});
it("should work for JSON content #3 - array of JSON objects", () => {
it("Should work for JSON content #3 - array of JSON objects", () => {
env.ACCOUNT_NAME = "azureaccount";
const replaceableVariables = { ACCOUNT_NAME: "fakestorageaccount" };
verifyFilterFunctionForJson(
@ -340,7 +341,7 @@ ultramarine.com/url/PUBLIC
);
});
it("should work for JSON content #4 - JSON content with key-value pair strings", () => {
it("Should work for JSON content #4 - JSON content with key-value pair strings", () => {
verifyFilterFunctionForJson(
{
response:
@ -358,7 +359,7 @@ ultramarine.com/url/PUBLIC
);
});
it("should work for JSON content #5 - regex to be replaced is present as a key-value pair in the JSON content", () => {
it("Should work for JSON content #5 - regex to be replaced is present as a key-value pair in the JSON content", () => {
verifyFilterFunctionForJson(
{ access_token: "eyJ0eXA75E_Q" },
{},
@ -370,7 +371,7 @@ ultramarine.com/url/PUBLIC
);
});
it("should work for JSON content #6 - JSON.stringify-ed content with regex to be replaced is present as a key-value pair at the top level in the JSON content", () => {
it("Should work for JSON content #6 - JSON.stringify-ed content with regex to be replaced is present as a key-value pair at the top level in the JSON content", () => {
verifyFilterFunctionForJson(
JSON.stringify({ access_token: "eyJ0eXA75E_Q" }),
{},
@ -382,7 +383,7 @@ ultramarine.com/url/PUBLIC
);
});
it("should work for JSON content #7 - JSON.stringify-ed content - regex to be replaced is present as a key-value pair somewhere inside the tree in the JSON content", () => {
it("Should work for JSON content #7 - JSON.stringify-ed content - regex to be replaced is present as a key-value pair somewhere inside the tree in the JSON content", () => {
verifyFilterFunctionForJson(
JSON.stringify({
recording: [{ access_token: "eyJ0eXA75E_Q" }]
@ -400,21 +401,21 @@ ultramarine.com/url/PUBLIC
});
describe("set environment variables", () => {
it("should not fail if the dictionary is empty", () => {
it("Should not fail if the dictionary is empty", () => {
env.SECRET = "SECRET";
const replaceableVariables = {};
setEnvironmentVariables(env, replaceableVariables);
});
it("should succeed if the dictionary has one key-value pair", () => {
it("Should succeed if the dictionary has one key-value pair", () => {
const replaceableVariables = { SECRET: "FAKE_IT" };
setEnvironmentVariables(env, replaceableVariables);
expect(env.SECRET).to.equal("FAKE_IT");
});
it("should succeed if the dictionary has multiple key-value pairs", () => {
it("Should succeed if the dictionary has multiple key-value pairs", () => {
const replaceableVariables = { ACCOUNT_NAME: "fake_account_name", SECRET: "FAKE IT" };
setEnvironmentVariables(env, replaceableVariables);
@ -422,4 +423,26 @@ ultramarine.com/url/PUBLIC
expect(env.ACCOUNT_NAME).to.equal("fake_account_name");
});
});
describe("generateTestRecordingFilePath", () => {
it("Should generate a properly formatted path on platform: Node", function() {
const platform = "node";
const testSuiteTitle = this.test!.parent!.fullTitle();
const testTitle = this.test!.title;
const result = generateTestRecordingFilePath(platform, testSuiteTitle, testTitle);
expect(result).to.equal(
`${platform}/utils_generatetestrecordingfilepath/recording_should_generate_a_properly_formatted_path_on_platform_node.js`
);
});
it("Should generate a properly formatted path on platform: Browsers", function() {
const platform = "browsers";
const testSuiteTitle = this.test!.parent!.fullTitle();
const testTitle = this.test!.title;
const result = generateTestRecordingFilePath(platform, testSuiteTitle, testTitle);
expect(result).to.equal(
`${platform}/utils_generatetestrecordingfilepath/recording_should_generate_a_properly_formatted_path_on_platform_browsers.json`
);
});
});
});

Просмотреть файл

@ -4,7 +4,7 @@
// https://github.com/karma-runner/karma-chrome-launcher
process.env.CHROME_BIN = require("puppeteer").executablePath();
require("dotenv").config();
const { jsonRecordingFilterFunction, isPlaybackMode } = require("@azure/test-utils-recorder");
const { jsonRecordingFilterFunction, isPlaybackMode, isSoftRecordMode, isRecordMode } = require("@azure/test-utils-recorder");
module.exports = function(config) {
config.set({
@ -32,7 +32,7 @@ module.exports = function(config) {
// list of files / patterns to load in the browser
files: ["dist-test/index.browser.js"].concat(
isPlaybackMode() ? ["recordings/browsers/**/*.json"] : []
(isPlaybackMode() || isSoftRecordMode()) ? ["recordings/browsers/**/*.json"] : []
),
// list of files / patterns to exclude
@ -127,7 +127,7 @@ module.exports = function(config) {
browserDisconnectTimeout: 10000,
browserDisconnectTolerance: 3,
browserConsoleLogOptions: {
terminal: process.env.TEST_MODE !== "record"
terminal: !isRecordMode()
},
client: {