diff --git a/.gitignore b/.gitignore index 7c4f084..ff58439 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ node_modules *.xml notification-domain/ TODO.txt -*.sh \ No newline at end of file +*.sh +coverage/ \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8205164..9f1cae4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,8 +28,8 @@ preparation: - echo "USER_MANAGER_PROD_IMAGE_NAME=${IMAGE_BASE}/user-manager:prod-${BUILD_ID}" >> context.env - echo "USER_MANAGER_TEST_IMAGE_NAME=${IMAGE_BASE}/user-manager:test-${BUILD_ID}" >> context.env - - echo "SUBSCRIPTION_PROD_IMAGE_NAME=${IMAGE_BASE}/screens-client:prod-${BUILD_ID}" >> context.env - - echo "SUBSCRIPTION_TEST_IMAGE_NAME=${IMAGE_BASE}/screens-client:test-${BUILD_ID}" >> context.env + - echo "SUBSCRIPTION_PROD_IMAGE_NAME=${IMAGE_BASE}/subs-manager:prod-${BUILD_ID}" >> context.env + - echo "SUBSCRIPTION_TEST_IMAGE_NAME=${IMAGE_BASE}/subs-manager:test-${BUILD_ID}" >> context.env - echo "SCREEN_CLIENT_PROD_IMAGE_NAME=${IMAGE_BASE}/screens-client:prod-${BUILD_ID}" >> context.env - echo "SCREEN_CLIENT_TEST_IMAGE_NAME=${IMAGE_BASE}/screens-client:test-${BUILD_ID}" >> context.env @@ -285,35 +285,64 @@ test-gateway: - job: build-gateway artifacts: true -test-integration: +test-browser-client: stage: test + coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/ tags: - dev script: - export $(cat context.env | xargs) + - docker login -u $CI_REGISTRY_USER --password $CI_JOB_TOKEN $CI_REGISTRY - - export API_IMAGE=$FLIGHTS_INFO_TEST_IMAGE_NAME - - export TEST_TARGET=INTEGRATION - - docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE down - - docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull - - docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d - - - export API_IMAGE=$GATEWAY_TEST_IMAGE_NAME - - export TEST_TARGET=INTEGRATION - - docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE down - - docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE pull - - docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE up -d - - - export API_IMAGE=$USER_MANAGER_TEST_IMAGE_NAME - - export TEST_TARGET=INTEGRATION - - docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE down - - docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull - - docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d + - export CLIENT_IMAGE=$BROWSER_CLIENT_TEST_IMAGE_NAME + - docker compose -f browser-domain/docker-compose.yml --env-file $ENV_DEV_FILE down + - docker compose -f browser-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull + - docker compose -f browser-domain/docker-compose.yml --env-file $ENV_DEV_FILE up --abort-on-container-exit + - docker cp fids_browser_client:/app/coverage/cobertura-coverage.xml . + - docker cp fids_browser_client:/app/junit.xml . + artifacts: + when: always + paths: + - cobertura-coverage.xml + reports: + junit: junit.xml + coverage_report: + coverage_format: cobertura + path: cobertura-coverage.xml needs: - - job: test-flights-api - - job: test-auth-api - job: preparation + - job: build-browser-client + artifacts: true + +test-screen-client: + stage: test + coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/ + tags: + - dev + script: + - export $(cat context.env | xargs) + + - docker login -u $CI_REGISTRY_USER --password $CI_JOB_TOKEN $CI_REGISTRY + + - export CLIENT_IMAGE=$SCREEN_CLIENT_TEST_IMAGE_NAME + - docker compose -f screen-domain/docker-compose.yml --env-file $ENV_DEV_FILE down + - docker compose -f screen-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull + - docker compose -f screen-domain/docker-compose.yml --env-file $ENV_DEV_FILE up --abort-on-container-exit + - docker cp fids_screens_client:/app/coverage/cobertura-coverage.xml . + - docker cp fids_screens_client:/app/junit.xml . + artifacts: + when: always + paths: + - cobertura-coverage.xml + reports: + junit: junit.xml + coverage_report: + coverage_format: cobertura + path: cobertura-coverage.xml + needs: + - job: preparation + - job: build-screen-client artifacts: true test-browser-integration: @@ -323,14 +352,39 @@ test-browser-integration: script: - export $(cat context.env | xargs) - docker login -u $CI_REGISTRY_USER --password $CI_JOB_TOKEN $CI_REGISTRY + + - export TEST_TARGET=INTEGRATION + + - export API_IMAGE=$FLIGHTS_INFO_TEST_IMAGE_NAME + - docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE down + - docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull + - docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d + + - export API_IMAGE=$GATEWAY_TEST_IMAGE_NAME + - docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE down + - docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE pull + - docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE up -d + + - export API_IMAGE=$USER_MANAGER_TEST_IMAGE_NAME + - docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE down + - docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull + - docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d + - docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE exec usermanager-api python manage.py recreate_db + - docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE exec usermanager-api python manage.py seed_db + + - export API_IMAGE=$SUBSCRIPTION_TEST_IMAGE_NAME + - docker compose -f subscription-domain/docker-compose.yml --env-file $ENV_DEV_FILE down + - docker compose -f subscription-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull + - docker compose -f subscription-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d - export CLIENT_IMAGE=$BROWSER_CLIENT_TEST_IMAGE_NAME - docker compose -f browser-domain/docker-compose.yml --env-file $ENV_DEV_FILE down - docker compose -f browser-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull - docker compose -f browser-domain/docker-compose.yml --env-file $ENV_DEV_FILE up --abort-on-container-exit needs: - - job: test-integration - - job: build-browser-client + - job: test-flights-api + - job: test-auth-api + - job: test-browser-client - job: preparation artifacts: true @@ -341,14 +395,39 @@ test-screen-integration: script: - export $(cat context.env | xargs) - docker login -u $CI_REGISTRY_USER --password $CI_JOB_TOKEN $CI_REGISTRY + + - export TEST_TARGET=INTEGRATION + + - export API_IMAGE=$FLIGHTS_INFO_TEST_IMAGE_NAME + - docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE down + - docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull + - docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d + + - export API_IMAGE=$GATEWAY_TEST_IMAGE_NAME + - docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE down + - docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE pull + - docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE up -d + + - export API_IMAGE=$USER_MANAGER_TEST_IMAGE_NAME + - docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE down + - docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull + - docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d + - docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE exec usermanager-api python manage.py recreate_db + - docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE exec usermanager-api python manage.py seed_db + + - export API_IMAGE=$SUBSCRIPTION_TEST_IMAGE_NAME + - docker compose -f subscription-domain/docker-compose.yml --env-file $ENV_DEV_FILE down + - docker compose -f subscription-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull + - docker compose -f subscription-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d - export CLIENT_IMAGE=$SCREEN_CLIENT_TEST_IMAGE_NAME - docker compose -f screen-domain/docker-compose.yml --env-file $ENV_DEV_FILE down - docker compose -f screen-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull - docker compose -f screen-domain/docker-compose.yml --env-file $ENV_DEV_FILE up --abort-on-container-exit needs: - - job: test-integration - - job: build-screen-client + - job: test-flights-api + - job: test-auth-api + - job: test-screen-client - job: preparation artifacts: true diff --git a/browser-domain/docker-compose.yml b/browser-domain/docker-compose.yml index 2d28688..9218174 100644 --- a/browser-domain/docker-compose.yml +++ b/browser-domain/docker-compose.yml @@ -7,4 +7,5 @@ services: restart: always environment: - API_HOST=api + - TEST_TARGET=${TEST_TARGET} network_mode: host diff --git a/browser-domain/jest.config.js b/browser-domain/jest.config.js index 2bd3148..51c5591 100644 --- a/browser-domain/jest.config.js +++ b/browser-domain/jest.config.js @@ -2,4 +2,5 @@ module.exports = { preset: "ts-jest", testEnvironment: "jsdom", + coverageReporters: ["html", "text", "text-summary", "cobertura"], }; diff --git a/browser-domain/package-lock.json b/browser-domain/package-lock.json index 0f01c29..ac16c40 100644 --- a/browser-domain/package-lock.json +++ b/browser-domain/package-lock.json @@ -28,6 +28,8 @@ "@types/react-dom": "^18.0.11", "jest": "^28.0.0", "jest-environment-jsdom": "^28.0.0", + "jest-junit": "^16.0.0", + "supertest": "^6.3.3", "ts-jest": "^28.0.0", "typescript": "^4.9.5" } @@ -5703,6 +5705,12 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -5809,6 +5817,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true + }, "node_modules/copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", @@ -6498,6 +6512,16 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -7716,6 +7740,12 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -8099,6 +8129,21 @@ "node": ">= 6" } }, + "node_modules/formidable": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", + "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "dev": true, + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -8499,6 +8544,15 @@ "he": "bin/he" } }, + "node_modules/hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -10331,6 +10385,33 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/jest-junit": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz", + "integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/jest-junit/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jest-leak-detector": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", @@ -16922,6 +17003,85 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/superagent": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", + "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", + "dev": true, + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.1.2", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=6.4.0 <13 || >=14" + } + }, + "node_modules/superagent/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/superagent/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/supertest": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz", + "integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==", + "dev": true, + "dependencies": { + "methods": "^1.1.2", + "superagent": "^8.0.5" + }, + "engines": { + "node": ">=6.4.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -18575,6 +18735,12 @@ } } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true + }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", @@ -22672,6 +22838,12 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -22762,6 +22934,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true + }, "copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", @@ -23243,6 +23421,16 @@ } } }, + "dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -24155,6 +24343,12 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, "fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -24432,6 +24626,18 @@ "mime-types": "^2.1.12" } }, + "formidable": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", + "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "dev": true, + "requires": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -24703,6 +24909,12 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -26050,6 +26262,26 @@ } } }, + "jest-junit": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz", + "integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, "jest-leak-detector": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", @@ -30698,6 +30930,66 @@ } } }, + "superagent": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", + "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", + "dev": true, + "requires": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.1.2", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "supertest": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz", + "integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^8.0.5" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -31940,6 +32232,12 @@ "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "requires": {} }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true + }, "xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", diff --git a/browser-domain/package.json b/browser-domain/package.json index 54d4b01..33db8ad 100644 --- a/browser-domain/package.json +++ b/browser-domain/package.json @@ -1,5 +1,5 @@ { - "name": "sample-client-users", + "name": "browser-client", "version": "0.1.0", "private": true, "dependencies": { @@ -16,11 +16,9 @@ "scripts": { "start": "react-scripts start", "build": "react-scripts build", - "test": "jest --coverage --collectCoverageFrom=\"./src/**\"", - "test:integration": "jest integration", - "eject": "react-scripts eject", - "docker:build": "docker build -t client-users .", - "docker:run": " docker run --rm -it -p 8080:80 client-users" + "test": "jest --testPathPattern=test.tsx --coverage --collectCoverageFrom=\"./src/**\" --reporters=default --reporters=jest-junit", + "test:integration": "jest integration --testPathPattern=src/tests/integration", + "eject": "react-scripts eject" }, "eslintConfig": { "extends": [ @@ -50,6 +48,8 @@ "@types/react-dom": "^18.0.11", "jest": "^28.0.0", "jest-environment-jsdom": "^28.0.0", + "jest-junit": "^16.0.0", + "supertest": "^6.3.3", "ts-jest": "^28.0.0", "typescript": "^4.9.5" } diff --git a/browser-domain/src/Api.ts b/browser-domain/src/Api.ts index 9e6424d..6e48789 100644 --- a/browser-domain/src/Api.ts +++ b/browser-domain/src/Api.ts @@ -2,55 +2,55 @@ import { Axios, AxiosError } from "axios"; import { Credentials, Token, User, Flight, FlightCreate } from "./Types"; const instance = new Axios({ - baseURL: process.env.REACT_APP_ENDPOINT ? process.env.REACT_APP_ENDPOINT : "http://127.0.0.1:5000/", - headers: { - accept: "application/json", - "Content-Type": "application/json", - }, + baseURL: process.env.REACT_APP_ENDPOINT ? process.env.REACT_APP_ENDPOINT : "http://127.0.0.1:5000/", + headers: { + accept: "application/json", + "Content-Type": "application/json", + }, validateStatus: (x) => { return !(x < 200 || x > 204) } }); instance.interceptors.request.use((request) => { - request.data = JSON.stringify(request.data); - return request; + request.data = JSON.stringify(request.data); + return request; }); instance.interceptors.response.use( - (response) => { - return JSON.parse(response.data); - }, - (error) => { - const err = error as AxiosError; - return Promise.reject(err); - } + (response) => { + return JSON.parse(response.data); + }, + (error) => { + const err = error as AxiosError; + return Promise.reject(err); + } ); export const createUser = ( - credentials: Credentials + credentials: Credentials ): Promise<{ id?: string; message: string }> => { - return instance.post("users", credentials); + return instance.post("users", credentials); }; export const fetchUsers = (): Promise => { - return instance.get("users"); + return instance.get("users"); }; export const fetchUserById = (id: number): Promise => { - return instance.get("users/" + id); + return instance.get("users/" + id); }; export const logIn = ( - credentials: Credentials + credentials: Credentials ): Promise> => { - return instance.post("auth/login", credentials); + return instance.post("auth/login", credentials); }; export const tokenStatus = ( - token: string + token: string ): Promise => { - return instance.get("auth/status", { - headers: { Authorization: `Bearer ${token}` }, - }); + return instance.get("auth/status", { + headers: { Authorization: `Bearer ${token}` }, + }); }; export const fetchZones = (origin: string | null): Promise => { @@ -58,10 +58,10 @@ export const fetchZones = (origin: string | null): Promise => { }; export const createFlight = ( - flight_data: FlightCreate, + flight_data: FlightCreate, token: string ): Promise => { - return instance.post("flights", flight_data, { - headers: { Authorization: `Bearer ${token}` }, - }); + return instance.post("flights", flight_data, { + headers: { Authorization: `Bearer ${token}` }, + }); }; \ No newline at end of file diff --git a/browser-domain/src/App.tsx b/browser-domain/src/App.tsx index 69bf6db..84d009e 100644 --- a/browser-domain/src/App.tsx +++ b/browser-domain/src/App.tsx @@ -5,18 +5,18 @@ import { Home } from "./components/Home/Home"; import { CreateFlight } from "./components/CreateFlight/CreateFlight"; import { Button } from "antd"; import useAuth, { AuthProvider } from "./useAuth"; - - function Router() { + +function Router() { const { user, logout, isAirline } = useAuth(); return ( -
+
} /> } /> - :} /> - :} /> - :} /> + : } /> + : } /> + : } />
{ @@ -28,16 +28,16 @@ import useAuth, { AuthProvider } from "./useAuth"; }
-
+
); - } +} function App() { - return ( + return ( - + - ); + ); } export default App; diff --git a/browser-domain/src/Types.d.ts b/browser-domain/src/Types.d.ts index b27e199..6b19936 100644 --- a/browser-domain/src/Types.d.ts +++ b/browser-domain/src/Types.d.ts @@ -1,29 +1,29 @@ export interface Credentials { - password: string; - email: string; - username?: string; + password: string; + email: string; + username?: string; } export interface Token { - refresh_token: string; - access_token: string; + refresh_token: string; + access_token: string; } export interface TokenData { - sub: string; - airline: boolean; + sub: string; + airline: boolean; } export interface User { - id: number; - username: string; - email: string; - created_date?: Date; + id: number; + username: string; + email: string; + created_date?: Date; } export interface Zone { - id: number; - name: string; + id: number; + name: string; } export interface Flight { @@ -35,9 +35,9 @@ export interface Flight { departure_time: string; arrival_time: string; gate: string; - } +} - export interface FlightCreate { +export interface FlightCreate { flight_code: string; status: string; origin: string; @@ -45,4 +45,4 @@ export interface Flight { departure_time: string; arrival_time: string; gate: string; - } \ No newline at end of file +} \ No newline at end of file diff --git a/browser-domain/src/components/Button/Button.test.tsx b/browser-domain/src/components/Button/Button.test.tsx index c705055..8456a87 100644 --- a/browser-domain/src/components/Button/Button.test.tsx +++ b/browser-domain/src/components/Button/Button.test.tsx @@ -5,13 +5,13 @@ import { render, screen } from "@testing-library/react"; import { Button } from "antd"; describe("Button Component Test", () => { - test("Display button label and clicked", async () => { - const onClick = jest.fn(); + test("Display button label and clicked", async () => { + const onClick = jest.fn(); - render(); + render(); - expect(screen.getByText("Button")).toBeVisible(); - await userEvent.click(screen.getByText("Button")); - expect(onClick).toBeCalled(); - }); + expect(screen.getByText("Button")).toBeVisible(); + await userEvent.click(screen.getByText("Button")); + expect(onClick).toBeCalled(); + }); }); diff --git a/browser-domain/src/components/CreateFlight/CreateFlight.tsx b/browser-domain/src/components/CreateFlight/CreateFlight.tsx index 54b4c6d..3186794 100644 --- a/browser-domain/src/components/CreateFlight/CreateFlight.tsx +++ b/browser-domain/src/components/CreateFlight/CreateFlight.tsx @@ -5,11 +5,9 @@ import "./FlightForm.css"; import { createFlight } from "../../Api"; export const CreateFlight = () => { - const urlParams = new URLSearchParams(window.location.search); - const origin = urlParams.get('origin'); const navigate = useNavigate(); - const [error, setError] = useState(null); - const [flight, setFlight] = useState(); + const [error, setError] = useState(null); + const [flight, setFlight] = useState(); const [flightData, setFlightData] = useState({ flight_code: "ABC123", @@ -19,9 +17,9 @@ export const CreateFlight = () => { departure_time: "2023-10-09 10:00 AM", arrival_time: "2023-10-09 12:00 PM", gate: "A1", - }); - - const handleSubmit = async (event: React.FormEvent) => { + }); + + const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); setError(null); @@ -40,79 +38,79 @@ export const CreateFlight = () => { .catch((error) => { setError(error as string); }); - }; + }; - return ( + return (
- - - - - - - - -
- ); + + + + + + + + + + ); }; diff --git a/browser-domain/src/components/CreateFlight/FlightForm.css b/browser-domain/src/components/CreateFlight/FlightForm.css index 5fb7614..4038ba3 100644 --- a/browser-domain/src/components/CreateFlight/FlightForm.css +++ b/browser-domain/src/components/CreateFlight/FlightForm.css @@ -5,26 +5,26 @@ border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - } - - label { +} + +label { display: block; margin-bottom: 10px; - } - - input { +} + +input { width: 100%; padding: 8px; margin-top: 4px; margin-bottom: 10px; box-sizing: border-box; - } - - button { +} + +button { background-color: #4caf50; color: white; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; - } \ No newline at end of file +} \ No newline at end of file diff --git a/browser-domain/src/components/Home/Card/Card.test.tsx b/browser-domain/src/components/Home/Card/Card.test.tsx index c527342..1805d88 100644 --- a/browser-domain/src/components/Home/Card/Card.test.tsx +++ b/browser-domain/src/components/Home/Card/Card.test.tsx @@ -5,10 +5,10 @@ import "../../../matchMedia.mock"; import { Card } from "./Card"; describe("Card Component Test", () => { - test("Display initial, name and icon", async () => { - // render(); + test("Display initial, name and icon", async () => { + // render(); - // expect(screen.getByText("Belgrano📍")).toBeVisible(); - // expect(screen.getByText("B")).toBeVisible(); - }); + // expect(screen.getByText("Belgrano📍")).toBeVisible(); + // expect(screen.getByText("B")).toBeVisible(); + }); }); diff --git a/browser-domain/src/components/Home/Card/Card.tsx b/browser-domain/src/components/Home/Card/Card.tsx index 49db412..6832893 100644 --- a/browser-domain/src/components/Home/Card/Card.tsx +++ b/browser-domain/src/components/Home/Card/Card.tsx @@ -5,59 +5,59 @@ import { RightOutlined, ClockCircleOutlined, SwapOutlined, EnvironmentOutlined, import "./Card.css"; interface FlightProps { - flight_code: string; - status: string; - origin: string; - destination: string; - departure_time: string; - arrival_time: string; - gate: string; + flight_code: string; + status: string; + origin: string; + destination: string; + departure_time: string; + arrival_time: string; + gate: string; } interface CardProps { - flight: FlightProps; + flight: FlightProps; } const { Text } = Typography; export const Card: React.FC = ({ flight }) => { - return ( -
- - } /> -
- {flight.flight_code} -
- - {flight.origin} {flight.destination} - -
+ return ( +
+ + } /> +
+ {flight.flight_code} +
+ + {flight.origin} {flight.destination} + +
+
+
+
+ + Status: + {flight.status} + + + Departure: + + + {flight.departure_time} + + + + Arrival: + + + {flight.arrival_time} + + + + Gate: + {flight.gate} + +
- -
- - Status: - {flight.status} - - - Departure: - - - {flight.departure_time} - - - - Arrival: - - - {flight.arrival_time} - - - - Gate: - {flight.gate} - -
-
- ); + ); }; diff --git a/browser-domain/src/components/Home/Home.test.tsx b/browser-domain/src/components/Home/Home.test.tsx index b0940c0..cef05e9 100644 --- a/browser-domain/src/components/Home/Home.test.tsx +++ b/browser-domain/src/components/Home/Home.test.tsx @@ -1,8 +1,8 @@ const mockedUsedNavigate = jest.fn(); jest.mock("react-router-dom", () => ({ - ...jest.requireActual("react-router-dom"), - useNavigate: () => mockedUsedNavigate, + ...jest.requireActual("react-router-dom"), + useNavigate: () => mockedUsedNavigate, })); import "../../matchMedia.mock"; @@ -11,18 +11,18 @@ import { render, screen } from "@testing-library/react"; import { Home } from "./Home"; describe("Home View Test", () => { - test("Display initial, name and icon", async () => { - // render( - // - // ); + test("Display initial, name and icon", async () => { + // render( + // + // ); - // expect(screen.getByText("Zones")).toBeVisible(); - // expect(screen.getByText("Belgrano📍")).toBeVisible(); - // expect(screen.getByText("San Isidro📍")).toBeVisible(); - }); + // expect(screen.getByText("Zones")).toBeVisible(); + // expect(screen.getByText("Belgrano📍")).toBeVisible(); + // expect(screen.getByText("San Isidro📍")).toBeVisible(); + }); }); diff --git a/browser-domain/src/components/Home/Home.tsx b/browser-domain/src/components/Home/Home.tsx index 6e9b8c6..67566b6 100644 --- a/browser-domain/src/components/Home/Home.tsx +++ b/browser-domain/src/components/Home/Home.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React from "react"; import { Card } from "./Card/Card"; import { useFetchZones } from "../../hooks/useFetchZones"; import { Flight } from "../../Types"; @@ -6,13 +6,13 @@ import { useNavigate } from "react-router"; import useAuth from "../../useAuth"; interface Props { - flights?: Flight[]; + flights?: Flight[]; } export const Home: React.FC = (props) => { const urlParams = new URLSearchParams(window.location.search); const origin = urlParams.get('origin'); - const { zones, error } = useFetchZones(origin); + const { zones, error } = useFetchZones(origin); const navigate = useNavigate() const { loading, isAirline } = useAuth(); @@ -21,16 +21,16 @@ export const Home: React.FC = (props) => { return
Loading...
; } - return ( -
- {isAirline ? : <>} -

Flights

-
- {(props.flights ? props.flights : zones).map((u) => { - return ; - })} - {error ?
{error}
: <>} -
-
- ); + return ( +
+ {isAirline ? : <>} +

Flights

+
+ {(props.flights ? props.flights : zones).map((u) => { + return ; + })} + {error ?
{error}
: <>} +
+
+ ); }; diff --git a/browser-domain/src/components/LogIn/LogIn.tsx b/browser-domain/src/components/LogIn/LogIn.tsx index de64502..f42b772 100644 --- a/browser-domain/src/components/LogIn/LogIn.tsx +++ b/browser-domain/src/components/LogIn/LogIn.tsx @@ -4,42 +4,37 @@ import useAuth from "../../useAuth"; export const LogIn = () => { const { login, loading, error } = useAuth(); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); - return ( -
-
- logo -
- setEmail(ev.target.value)} - /> - setPassword(ev.target.value)} - /> - - {error ? ( -
{error}
- ) : ( - <> - )} -
-
-
- ); + return ( +
+
+
+ setEmail(ev.target.value)} + /> + setPassword(ev.target.value)} + /> + + {error ? ( +
{error}
+ ) : ( + <> + )} +
+
+
+ ); }; diff --git a/browser-domain/src/components/SignUp/SignUp.tsx b/browser-domain/src/components/SignUp/SignUp.tsx index 9d309a8..0cd1323 100644 --- a/browser-domain/src/components/SignUp/SignUp.tsx +++ b/browser-domain/src/components/SignUp/SignUp.tsx @@ -3,61 +3,56 @@ import { Button, Input } from "antd"; import { useCreateUser } from "../../hooks/useCreateUser"; export const SignUp = () => { - const [username, setUsername] = useState(""); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [repeatPassword, setRepeatPassword] = useState(""); + const [username, setUsername] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [repeatPassword, setRepeatPassword] = useState(""); - const { createUser, isLoading, error } = useCreateUser(); + const { createUser, isLoading, error } = useCreateUser(); - return ( -
-
- logo -
- setEmail(ev.target.value)} - /> - setUsername(ev.target.value)} - /> - setPassword(ev.target.value)} - /> - setRepeatPassword(ev.target.value)} - /> - - {error ? ( -
{error}
- ) : ( - <> - )} -
-
-
- ); + return ( +
+
+
+ setEmail(ev.target.value)} + /> + setUsername(ev.target.value)} + /> + setPassword(ev.target.value)} + /> + setRepeatPassword(ev.target.value)} + /> + + {error ? ( +
{error}
+ ) : ( + <> + )} +
+
+
+ ); }; diff --git a/browser-domain/src/hooks/useCreateFlight.tsx b/browser-domain/src/hooks/useCreateFlight.tsx index 49e1169..06d690f 100644 --- a/browser-domain/src/hooks/useCreateFlight.tsx +++ b/browser-domain/src/hooks/useCreateFlight.tsx @@ -4,11 +4,11 @@ import { User, Flight, FlightCreate } from "../Types"; import { createFlight } from "../Api"; export const useCreateFlight = (flight_data: FlightCreate) => { - const [error, setError] = useState(null); - const [flight, setFlight] = useState(); + const [error, setError] = useState(null); + const [flight, setFlight] = useState(); - useEffect(() => { - setError(null); + useEffect(() => { + setError(null); const token = localStorage.getItem("token"); if (!token) { @@ -16,14 +16,14 @@ export const useCreateFlight = (flight_data: FlightCreate) => { return; } - createFlight(flight_data, token) - .then((data) => { - setFlight(data); - }) - .catch((error) => { - setError(error as string); - }); - }, []); + createFlight(flight_data, token) + .then((data) => { + setFlight(data); + }) + .catch((error) => { + setError(error as string); + }); + }, []); - return { flight, error }; + return { flight, error }; }; diff --git a/browser-domain/src/hooks/useCreateUser.tsx b/browser-domain/src/hooks/useCreateUser.tsx index 4b1a7be..463cfb6 100644 --- a/browser-domain/src/hooks/useCreateUser.tsx +++ b/browser-domain/src/hooks/useCreateUser.tsx @@ -4,28 +4,28 @@ import { createUser as createUserAPI } from "../Api"; import useAuth from "../useAuth"; export const useCreateUser = () => { - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); const { login } = useAuth(); - const createUser = async (credentials: Credentials): Promise => { - try { - setIsLoading(true); - setError(null); + const createUser = async (credentials: Credentials): Promise => { + try { + setIsLoading(true); + setError(null); - const createResponse = await createUserAPI(credentials); + const createResponse = await createUserAPI(credentials); - if (createResponse.id) { - login(credentials); - } else { - setError(createResponse.message); - } - } catch (error) { - setError(error as string); - } finally { - setIsLoading(false); - } - }; + if (createResponse.id) { + login(credentials); + } else { + setError(createResponse.message); + } + } catch (error) { + setError(error as string); + } finally { + setIsLoading(false); + } + }; - return { createUser, isLoading, error }; + return { createUser, isLoading, error }; }; diff --git a/browser-domain/src/hooks/useFetchZones.tsx b/browser-domain/src/hooks/useFetchZones.tsx index 5e9e29a..5e3df8b 100644 --- a/browser-domain/src/hooks/useFetchZones.tsx +++ b/browser-domain/src/hooks/useFetchZones.tsx @@ -4,18 +4,18 @@ import { User, Flight } from "../Types"; import { fetchZones } from "../Api"; export const useFetchZones = (origin: string | null) => { - const [error, setError] = useState(null); - const [zones, setZones] = useState([]); + const [error, setError] = useState(null); + const [zones, setZones] = useState([]); - useEffect(() => { - setError(null); + useEffect(() => { + setError(null); - fetchZones(origin) - .then((data) => { - setZones(data); - }) - .catch((error) => {}); - }, []); + fetchZones(origin) + .then((data) => { + setZones(data); + }) + .catch((error) => { }); + }, []); - return { zones, error }; + return { zones, error }; }; diff --git a/browser-domain/src/index.css b/browser-domain/src/index.css index bd54b3b..a94edcd 100644 --- a/browser-domain/src/index.css +++ b/browser-domain/src/index.css @@ -1,106 +1,106 @@ body { - margin: 0; - font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", - "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", - "Helvetica Neue", sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + margin: 0; + font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", + "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", - monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; } .App { - width: 100vw; - height: 100vh; - display: flex; - justify-content: center; - align-items: center; - background-color: #eff2f7; + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background-color: #eff2f7; } .Box { - border-radius: 20px; - box-shadow: 0px 20px 60px rgba(0, 0, 0, 0.2); - padding: 50px; - gap: 30px; - background-color: white; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; + border-radius: 20px; + box-shadow: 0px 20px 60px rgba(0, 0, 0, 0.2); + padding: 50px; + gap: 30px; + background-color: white; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; } .Small { - width: 250px; - height: 400px; + width: 250px; + height: 400px; } .Section { - flex: 1; - width: 100%; - padding: 30px 50px; - gap: 30px; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; + flex: 1; + width: 100%; + padding: 30px 50px; + gap: 30px; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; } .Image { - width: 150px; + width: 150px; } .Connected { - color: green; + color: green; } .Disconnected { - color: red; + color: red; } .FloatingStatus { - position: absolute; - top: 10px; - right: 50px; + position: absolute; + top: 10px; + right: 50px; } .LogoutButton { - position: absolute; - bottom: 10px; - right: 50px; + position: absolute; + bottom: 10px; + right: 50px; } .Card { - border-radius: 8px; - box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.2); - gap: 10px; - padding: 10px; - width: 100%; - background-color: white; - display: flex; - align-items: center; + border-radius: 8px; + box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.2); + gap: 10px; + padding: 10px; + width: 100%; + background-color: white; + display: flex; + align-items: center; } .Items { - height: 100%; - width: 100%; - display: flex; + height: 100%; + width: 100%; + display: flex; flex-wrap: wrap; - justify-content: space-between; - align-items: center; + justify-content: space-between; + align-items: center; gap: 20px; } .List { - width: 100%; - height: 500px; - gap: 30px; - padding: 20px; - overflow-y: auto; - display: flex; - align-items: center; - flex-direction: column; -} + width: 100%; + height: 500px; + gap: 30px; + padding: 20px; + overflow-y: auto; + display: flex; + align-items: center; + flex-direction: column; +} \ No newline at end of file diff --git a/browser-domain/src/tests/integration/flights.spec.js b/browser-domain/src/tests/integration/flights.spec.js new file mode 100644 index 0000000..c9b21c1 --- /dev/null +++ b/browser-domain/src/tests/integration/flights.spec.js @@ -0,0 +1,77 @@ +const request = require('supertest'); +const app = 'http://127.0.0.1:5000' + +const authCredentials = { + email: 'info@lufthansa.com', + password: 'password1234', +}; + +describe('Flight API Endpoints', () => { + let authToken; + + beforeAll(async () => { + const loginResponse = await request(app) + .post('/auth/login') + .send(authCredentials); + + expect(loginResponse.statusCode).toBe(200); + expect(loginResponse.body).toHaveProperty('access_token'); + authToken = loginResponse.body.access_token; + }); + + it('should create a new flight', async () => { + const newFlight = { + "flight_code": "ABC127", + "status": "En ruta", + "origin": "Frankfurt", + "destination": "Ciudad A", + "departure_time": "2023-10-29 09:57 PM", + "arrival_time": "2023-10-30 12:00 PM", + "gate": "A2" + }; + + const response = await request(app) + .post('/flights') + .send(newFlight) + .set('Authorization', `Bearer ${authToken}`); + + expect(response.statusCode).toBe(200); + expect(response.body).toHaveProperty('id'); + createdFlightId = response.body.id; + }); + + it('should retrieve the created flight by ID', async () => { + const response = await request(app) + .get(`/flights/${createdFlightId}`) + .set('Authorization', `Bearer ${authToken}`); + + expect(response.statusCode).toBe(200); + expect(response.body).toHaveProperty('id', createdFlightId); + }); + + it('should update a flight by ID', async () => { + const flightUpdate = { + "status": "Delayed" + }; + + const response = await request(app) + .patch(`/flights/${createdFlightId}`) + .send(flightUpdate) + .set('Authorization', `Bearer ${authToken}`); + + expect(response.statusCode).toBe(200); + expect(response.body).toHaveProperty('id'); + expect(response.body.status).toBe('Delayed'); + }); + + it('should retrieve filtered flights', async () => { + const response = await request(app) + .get('/flights') + .query({ origin: 'Frankfurt' }) + .set('Authorization', `Bearer ${authToken}`); + + expect(response.statusCode).toBe(200); + expect(response.body).toBeInstanceOf(Array); + expect(response.body[0].id).toBe(createdFlightId) + }); +}); \ No newline at end of file diff --git a/browser-domain/src/useAuth.tsx b/browser-domain/src/useAuth.tsx index 9417c86..85d7a4b 100644 --- a/browser-domain/src/useAuth.tsx +++ b/browser-domain/src/useAuth.tsx @@ -1,10 +1,10 @@ -import React, {createContext, ReactNode, useContext, useEffect, useMemo, useState} from "react"; +import React, { createContext, ReactNode, useContext, useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router"; import { Credentials, TokenData, User } from "./Types"; import { fetchUserById, logIn, tokenStatus } from "./Api"; import jwt_decode from "jwt-decode"; - - interface AuthContextType { + +interface AuthContextType { user?: User; loading: boolean; isAirline: boolean; @@ -12,28 +12,28 @@ import jwt_decode from "jwt-decode"; login: (credentials: Credentials) => void; signUp: (email: string, name: string, password: string) => void; logout: () => void; - } - - const AuthContext = createContext( +} + +const AuthContext = createContext( {} as AuthContextType - ); - - export function AuthProvider({ +); + +export function AuthProvider({ children, - }: { +}: { children: ReactNode; - }): JSX.Element { +}): JSX.Element { const [user, setUser] = useState(); const [error, setError] = useState(); const [loading, setLoading] = useState(false); const [loadingInitial, setLoadingInitial] = useState(true); - const [isAirline, setIsAirline] = useState(false); + const [isAirline, setIsAirline] = useState(false); const navigate = useNavigate(); - + useEffect(() => { - if (error) setError(undefined); + if (error) setError(undefined); }, [window.location.pathname]); - + useEffect(() => { const existingToken = localStorage.getItem("token"); if (existingToken) { @@ -48,68 +48,68 @@ import jwt_decode from "jwt-decode"; } tokenStatus(existingToken) - .then((res) => fetchUserById(res.id) - .then((res) => setUser(res)) - .catch((_error) => {}) - .finally(() => setLoadingInitial(false)) - ) - .catch((_error) => { - setLoadingInitial(false) - logout() - }) + .then((res) => fetchUserById(res.id) + .then((res) => setUser(res)) + .catch((_error) => { }) + .finally(() => setLoadingInitial(false)) + ) + .catch((_error) => { + setLoadingInitial(false) + logout() + }) // .finally(() => setLoadingInitial(false)); } else { setLoadingInitial(false) } }, []); - + function login(credentials: Credentials) { setLoading(true); const tokens = logIn(credentials) - .then((x) => { - localStorage.setItem("token", x.access_token); - const airline = (jwt_decode(x.access_token) as TokenData).airline; - setIsAirline(airline) - const user = fetchUserById(x.user_id as number) - .then(y => { - setUser(y); - navigate("/home") + .then((x) => { + localStorage.setItem("token", x.access_token); + const airline = (jwt_decode(x.access_token) as TokenData).airline; + setIsAirline(airline) + const user = fetchUserById(x.user_id as number) + .then(y => { + setUser(y); + navigate("/home") + }) + .catch((error) => setError(error)) + .finally(() => setLoading(false)); }) .catch((error) => setError(error)) - .finally(() => setLoading(false)); - }) - .catch((error) => setError(error)) // .finally(() => setLoading(false)); } - - function signUp(email: string, name: string, password: string) {} - + + function signUp(email: string, name: string, password: string) { } + function logout() { localStorage.removeItem("token"); - setUser(undefined); + setUser(undefined); navigate("/login") } const memoedValue = useMemo( - () => ({ - user, - loading, - isAirline, - error, - login, - signUp, - logout, - }), - [user, isAirline, loading, error] + () => ({ + user, + loading, + isAirline, + error, + login, + signUp, + logout, + }), + [user, isAirline, loading, error] ); - + return ( - - {!loadingInitial && children} - + + {!loadingInitial && children} + ); - } - - export default function useAuth() { +} + +export default function useAuth() { return useContext(AuthContext); - } \ No newline at end of file +} \ No newline at end of file diff --git a/browser-domain/test.sh b/browser-domain/test.sh index c20a79c..57664cd 100644 --- a/browser-domain/test.sh +++ b/browser-domain/test.sh @@ -1,8 +1,7 @@ #!/bin/bash -curl -X DELETE api:5000/ping -curl -X POST api:5000/ping - - -# npm test -echo "NPM TEST" +if [ "${TEST_TARGET:-}" = "INTEGRATION" ]; then + npm run test:integration +else + npm run test +fi diff --git a/flights-domain/flights-information/src/api/cruds/flight.py b/flights-domain/flights-information/src/api/cruds/flight.py index 130be7c..cd1f159 100644 --- a/flights-domain/flights-information/src/api/cruds/flight.py +++ b/flights-domain/flights-information/src/api/cruds/flight.py @@ -1,3 +1,4 @@ +from sqlalchemy import Date from sqlalchemy.orm import Session from sqlalchemy.sql import func @@ -48,9 +49,29 @@ def get_flights_by_origin(db: Session, origin: str): return db.query(Flight).filter(Flight.origin == origin).all() -def get_flights_update(db: Session, origin: str, lastUpdate: str): +def get_flights_by_destination(db: Session, destination: str): + return db.query(Flight).filter(Flight.destination == destination).all() + + +def get_flights_update_origin(db: Session, origin: str, lastUpdate: str): return ( db.query(Flight) - .filter((Flight.origin == origin) & (Flight.last_updated >= lastUpdate)) + .filter( + (Flight.origin == origin) + & (Flight.last_updated >= lastUpdate) + & (Flight.departure_time.cast(Date) >= func.current_date()) + ) + .all() + ) + + +def get_flights_update_destination(db: Session, destination: str, lastUpdate: str): + return ( + db.query(Flight) + .filter( + (Flight.destination == destination) + & (Flight.last_updated >= lastUpdate) + & (Flight.arrival_time.cast(Date) >= func.current_date()) + ) .all() ) diff --git a/flights-domain/flights-information/src/api/routes/flights.py b/flights-domain/flights-information/src/api/routes/flights.py index cb7c178..9b1ba67 100644 --- a/flights-domain/flights-information/src/api/routes/flights.py +++ b/flights-domain/flights-information/src/api/routes/flights.py @@ -51,13 +51,20 @@ async def update_flight( @router.get("", response_model=list[Flight]) def get_flights( origin: Optional[str] = None, + destination: Optional[str] = None, lastUpdated: Optional[str] = None, db: Session = Depends(get_db), ): if origin and lastUpdated: - flights = flight_crud.get_flights_update(db, origin, lastUpdated) + flights = flight_crud.get_flights_update_origin(db, origin, lastUpdated) + elif destination and lastUpdated: + flights = flight_crud.get_flights_update_destination( + db, destination, lastUpdated + ) elif origin: - flights = flight_crud.get_flights_by_origin(db, origin) + flights = flight_crud.get_flights_by_origin(db=db, origin=origin) + elif destination: + flights = flight_crud.get_flights_by_destination(db=db, destination=destination) else: flights = flight_crud.get_flights(db=db) diff --git a/gateway/src/api/routes/flights.py b/gateway/src/api/routes/flights.py index f1315c2..45bce7f 100644 --- a/gateway/src/api/routes/flights.py +++ b/gateway/src/api/routes/flights.py @@ -51,10 +51,12 @@ async def update_flight( @router.get("", response_model=list[Flight]) -async def get_flights(origin: Optional[str] = None, lastUpdated: Optional[str] = None): +async def get_flights(origin: Optional[str] = None, destination: Optional[str] = None, lastUpdated: Optional[str] = None): query = {} if origin: query["origin"] = origin + if destination: + query["destination"] = destination if lastUpdated: query["lastUpdated"] = lastUpdated (response, status, _) = await request(f"{API_FLIGHTS}", "GET", query=query) diff --git a/run.sh b/run.sh index ce76fb8..ed5c5fa 100755 --- a/run.sh +++ b/run.sh @@ -145,13 +145,13 @@ elif [ -n "$domain" ] && [ -z "$down" ]; then fi ;; 'screen') - docker build screen-domain -f screen-domain/Dockerfile.prod --build-arg "REACT_APP_ORIGIN=$REACT_APP_ORIGIN" -t $USER/screen-client:prod - export CLIENT_IMAGE=$USER/screen-client:prod + docker build screen-domain -f screen-domain/Dockerfile.test --build-arg "REACT_APP_ORIGIN=$REACT_APP_ORIGIN" -t $USER/screen-client:test + export CLIENT_IMAGE=$USER/screen-client:test docker compose -f screen-domain/docker-compose.yml up -d ;; 'browser') - docker build browser-domain -f browser-domain/Dockerfile.prod -t $USER/browser-client:prod - export CLIENT_IMAGE=$USER/browser-client:prod + docker build browser-domain -f browser-domain/Dockerfile.test -t $USER/browser-client:test + export CLIENT_IMAGE=$USER/browser-client:test docker compose -f browser-domain/docker-compose.yml up -d ;; 'elk') @@ -183,6 +183,7 @@ else export SUBSCRIPTION_MANAGER=subscription-domain/subscription-manager docker build $SUBSCRIPTION_MANAGER -f $SUBSCRIPTION_MANAGER/Dockerfile.prod -t $USER/subs-manager:prod + export REACT_APP_ORIGIN=Frankfurt docker build screen-domain -f screen-domain/Dockerfile.prod --build-arg "REACT_APP_ORIGIN=$REACT_APP_ORIGIN" -t $USER/screen-client:prod docker build browser-domain -f browser-domain/Dockerfile.prod -t $USER/browser-client:prod @@ -205,4 +206,4 @@ else docker compose -f screen-domain/docker-compose.yml up -d export CLIENT_IMAGE=$USER/browser-client:prod docker compose -f browser-domain/docker-compose.yml up -d -fi \ No newline at end of file +fi diff --git a/screen-domain/Dockerfile.prod b/screen-domain/Dockerfile.prod index 8bf6e5a..552d8e6 100644 --- a/screen-domain/Dockerfile.prod +++ b/screen-domain/Dockerfile.prod @@ -1,7 +1,7 @@ FROM node:17.9.1 AS app WORKDIR /app -COPY package.json /app/package.json -RUN npm -v && ls -al +COPY package.json . +COPY package-lock.json . RUN npm install COPY . . diff --git a/screen-domain/docker-compose.yml b/screen-domain/docker-compose.yml index a0ff045..9a288b3 100644 --- a/screen-domain/docker-compose.yml +++ b/screen-domain/docker-compose.yml @@ -7,4 +7,5 @@ services: restart: always environment: - API_HOST=api + - TEST_TARGET=${TEST_TARGET} network_mode: host diff --git a/screen-domain/jest.config.js b/screen-domain/jest.config.js index 2bd3148..51c5591 100644 --- a/screen-domain/jest.config.js +++ b/screen-domain/jest.config.js @@ -2,4 +2,5 @@ module.exports = { preset: "ts-jest", testEnvironment: "jsdom", + coverageReporters: ["html", "text", "text-summary", "cobertura"], }; diff --git a/screen-domain/package-lock.json b/screen-domain/package-lock.json index 98a6dee..9470f99 100644 --- a/screen-domain/package-lock.json +++ b/screen-domain/package-lock.json @@ -15,6 +15,7 @@ "react-router": "^6.10.0", "react-router-dom": "^6.10.0", "react-scripts": "5.0.1", + "react-super-responsive-table": "^5.2.2", "web-vitals": "^2.1.4" }, "devDependencies": { @@ -27,6 +28,8 @@ "@types/react-dom": "^18.0.11", "jest": "^28.0.0", "jest-environment-jsdom": "^28.0.0", + "jest-junit": "^16.0.0", + "supertest": "^6.3.3", "ts-jest": "^28.0.0", "typescript": "^4.9.5" } @@ -5702,6 +5705,12 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -5808,6 +5817,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true + }, "node_modules/copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", @@ -6497,6 +6512,16 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -7715,6 +7740,12 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -8098,6 +8129,21 @@ "node": ">= 6" } }, + "node_modules/formidable": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", + "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "dev": true, + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -8498,6 +8544,15 @@ "he": "bin/he" } }, + "node_modules/hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -10330,6 +10385,33 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/jest-junit": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz", + "integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/jest-junit/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jest-leak-detector": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", @@ -15749,6 +15831,19 @@ "node": ">=10" } }, + "node_modules/react-super-responsive-table": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/react-super-responsive-table/-/react-super-responsive-table-5.2.2.tgz", + "integrity": "sha512-LDsGq8X3TBn0AMv5gX/0wFeWoFMMmiZXNTD1kIMMWz3WflPMHUZOSHXXlG5EOO2u3WSNcVl7MLIk83maS/Xj0g==", + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "prop-types": ">= 15", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -16916,6 +17011,85 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/superagent": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", + "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", + "dev": true, + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.1.2", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=6.4.0 <13 || >=14" + } + }, + "node_modules/superagent/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/superagent/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/supertest": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz", + "integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==", + "dev": true, + "dependencies": { + "methods": "^1.1.2", + "superagent": "^8.0.5" + }, + "engines": { + "node": ">=6.4.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -18569,6 +18743,12 @@ } } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true + }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", @@ -22666,6 +22846,12 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -22756,6 +22942,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true + }, "copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", @@ -23237,6 +23429,16 @@ } } }, + "dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -24149,6 +24351,12 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, "fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -24426,6 +24634,18 @@ "mime-types": "^2.1.12" } }, + "formidable": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", + "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "dev": true, + "requires": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -24697,6 +24917,12 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -26044,6 +26270,26 @@ } } }, + "jest-junit": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz", + "integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, "jest-leak-detector": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", @@ -29825,6 +30071,12 @@ } } }, + "react-super-responsive-table": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/react-super-responsive-table/-/react-super-responsive-table-5.2.2.tgz", + "integrity": "sha512-LDsGq8X3TBn0AMv5gX/0wFeWoFMMmiZXNTD1kIMMWz3WflPMHUZOSHXXlG5EOO2u3WSNcVl7MLIk83maS/Xj0g==", + "requires": {} + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -30687,6 +30939,66 @@ } } }, + "superagent": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", + "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", + "dev": true, + "requires": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.1.2", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "supertest": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz", + "integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^8.0.5" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -31929,6 +32241,12 @@ "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "requires": {} }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true + }, "xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", diff --git a/screen-domain/package.json b/screen-domain/package.json index 3e9b487..736d181 100644 --- a/screen-domain/package.json +++ b/screen-domain/package.json @@ -1,5 +1,5 @@ { - "name": "sample-client-users", + "name": "screen-client", "version": "0.1.0", "private": true, "dependencies": { @@ -10,16 +10,15 @@ "react-router": "^6.10.0", "react-router-dom": "^6.10.0", "react-scripts": "5.0.1", + "react-super-responsive-table": "^5.2.2", "web-vitals": "^2.1.4" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", - "test": "jest --coverage --collectCoverageFrom=\"./src/**\"", - "test:integration": "jest integration", - "eject": "react-scripts eject", - "docker:build": "docker build -t client-users .", - "docker:run": " docker run --rm -it -p 8080:80 client-users" + "test": "jest --testPathPattern=test.tsx --coverage --collectCoverageFrom=\"./src/**\" --reporters=default --reporters=jest-junit", + "test:integration": "jest integration --testPathPattern=src/tests/integration", + "eject": "react-scripts eject" }, "eslintConfig": { "extends": [ @@ -49,6 +48,8 @@ "@types/react-dom": "^18.0.11", "jest": "^28.0.0", "jest-environment-jsdom": "^28.0.0", + "jest-junit": "^16.0.0", + "supertest": "^6.3.3", "ts-jest": "^28.0.0", "typescript": "^4.9.5" } diff --git a/screen-domain/src/Api.ts b/screen-domain/src/Api.ts index 1454d22..aecca59 100644 --- a/screen-domain/src/Api.ts +++ b/screen-domain/src/Api.ts @@ -1,36 +1,37 @@ import { Axios, AxiosError } from "axios"; -import { Credentials, User, Flight } from "./Types"; +import { Flight } from "./Types"; const instance = new Axios({ - baseURL: process.env.REACT_APP_ENDPOINT ? process.env.REACT_APP_ENDPOINT : "http://127.0.0.1:5000/", - headers: { - accept: "application/json", - "Content-Type": "application/json", - }, + baseURL: process.env.REACT_APP_ENDPOINT ? process.env.REACT_APP_ENDPOINT : "http://127.0.0.1:5000/", + headers: { + accept: "application/json", + "Content-Type": "application/json", + }, validateStatus: (x) => { return !(x < 200 || x > 204) } }); instance.interceptors.response.use( - (response) => { - return JSON.parse(response.data); - }, - (error) => { - const err = error as AxiosError; - return Promise.reject(err); - } + (response) => { + return JSON.parse(response.data); + }, + (error) => { + const err = error as AxiosError; + return Promise.reject(err); + } ); instance.interceptors.request.use((request) => { - request.data = JSON.stringify(request.data); - return request; + request.data = JSON.stringify(request.data); + return request; }); export const ping = () => { - return instance.get("health"); + return instance.get("health"); }; -export const fetchZones = (origin: string | undefined, lastUpdate: string | null): Promise => { - return instance.get("flights" + - (origin ? "?origin=" + origin : "") + - (lastUpdate ? ( origin ? "&lastUpdated=" : "?lastUpdated=") + lastUpdate : "")) +export const fetchZones = (origin: string | undefined, destination: string | undefined, lastUpdate: string | null): Promise => { + return instance.get("flights" + + (origin ? "?origin=" + origin : "") + + (destination ? "?destination=" + destination : "") + + (lastUpdate ? (origin ? "&lastUpdated=" : "?lastUpdated=") + lastUpdate : "")) }; diff --git a/screen-domain/src/App.tsx b/screen-domain/src/App.tsx index 81fbb31..540c41c 100644 --- a/screen-domain/src/App.tsx +++ b/screen-domain/src/App.tsx @@ -1,23 +1,22 @@ -import React, { useEffect } from "react"; import { useIsConnected } from "./hooks/useIsConnected"; import { Route, Routes } from "react-router"; +import { Departure } from "./components/Home/Departure"; +import { Arrival } from "./components/Home/Arrival"; import { Home } from "./components/Home/Home"; -import { Button } from "antd"; -import { initDB } from "./db"; function App() { - const connection = useIsConnected(); - initDB(); + const connection = useIsConnected(); - return ( -
- - } /> - } /> - -
{connection}
-
- ); + return ( +
+ + } /> + } /> + } /> + +
{connection}
+
+ ); } export default App; diff --git a/screen-domain/src/Types.d.ts b/screen-domain/src/Types.d.ts index 8504219..730e041 100644 --- a/screen-domain/src/Types.d.ts +++ b/screen-domain/src/Types.d.ts @@ -1,24 +1,24 @@ export interface Credentials { - password: string; - email: string; - username?: string; + password: string; + email: string; + username?: string; } export interface Token { - refresh_token: string; - access_token: string; + refresh_token: string; + access_token: string; } export interface User { - id: number; - username: string; - email: string; - created_date?: Date; + id: number; + username: string; + email: string; + created_date?: Date; } export interface Zone { - id: number; - name: string; + id: number; + name: string; } export interface Flight { @@ -30,4 +30,4 @@ export interface Flight { departure_time: string; arrival_time: string; gate: string; - } \ No newline at end of file +} \ No newline at end of file diff --git a/screen-domain/src/components/Button/Button.test.tsx b/screen-domain/src/components/Button/Button.test.tsx index c705055..8456a87 100644 --- a/screen-domain/src/components/Button/Button.test.tsx +++ b/screen-domain/src/components/Button/Button.test.tsx @@ -5,13 +5,13 @@ import { render, screen } from "@testing-library/react"; import { Button } from "antd"; describe("Button Component Test", () => { - test("Display button label and clicked", async () => { - const onClick = jest.fn(); + test("Display button label and clicked", async () => { + const onClick = jest.fn(); - render(); + render(); - expect(screen.getByText("Button")).toBeVisible(); - await userEvent.click(screen.getByText("Button")); - expect(onClick).toBeCalled(); - }); + expect(screen.getByText("Button")).toBeVisible(); + await userEvent.click(screen.getByText("Button")); + expect(onClick).toBeCalled(); + }); }); diff --git a/screen-domain/src/components/Home/Arrival.tsx b/screen-domain/src/components/Home/Arrival.tsx new file mode 100644 index 0000000..b8cd8ce --- /dev/null +++ b/screen-domain/src/components/Home/Arrival.tsx @@ -0,0 +1,65 @@ +import React, { useEffect, useState } from "react"; +import { Card } from "./Card/Card"; +import { Flight } from "../../Types"; +import './Home.css' +import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table'; +import 'react-super-responsive-table/dist/SuperResponsiveTableStyle.css'; +import { useFetchDestination } from "../../hooks/useFetchDestination"; + +interface Props { + flights?: Flight[]; +} + +export const Arrival: React.FC = (props) => { + let destination = process.env.REACT_APP_ORIGIN; + + const { zones, error } = useFetchDestination(destination); + const [startIndex, setStartIndex] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + if (zones.length <= 10) { + return; + } + setStartIndex((prevIndex) => (prevIndex + 10) >= zones.length ? 0 : (prevIndex + 10)); + }, 5000); + + return () => clearInterval(interval); + }, [zones]); + + + return ( +
+

Arrival

+
+ + + + + + + + + + + + {zones.length > 0 && ( + <> + {zones.slice(startIndex, startIndex + 10).map((flight) => ( + + + + + + + + ))} + + )} + +
CodeOriginTimeGateStatus
{flight.flight_code}{flight.origin}{flight.arrival_time}{flight.gate}{flight.status}
+ {error ?
{error}
: <>} +
+
+ ); +}; diff --git a/screen-domain/src/components/Home/Card/Card.css b/screen-domain/src/components/Home/Card/Card.css index 8e01b69..0f52e3c 100644 --- a/screen-domain/src/components/Home/Card/Card.css +++ b/screen-domain/src/components/Home/Card/Card.css @@ -1,7 +1,8 @@ -/* .flight-card { +.flight-card { display: flex; + flex-direction: column; justify-content: space-between; - align-items: center; + align-items: flex-start; padding: 16px; border: 1px solid #ddd; border-radius: 8px; @@ -9,37 +10,14 @@ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); background-color: #fff; transition: box-shadow 0.3s ease; - - &:hover { - box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); - } - - .flight-details { - display: flex; - align-items: center; - } - } */ - .flight-card { - display: flex; - flex-direction: column; /* Display as a column instead of a row */ - justify-content: space-between; - align-items: flex-start; /* Align items to the start of the column */ - padding: 16px; - border: 1px solid #ddd; - border-radius: 8px; - margin-bottom: 16px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - background-color: #fff; - transition: box-shadow 0.3s ease; - &:hover { - box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); } - + .flight-details { - display: flex; - flex-direction: column; /* Display details as a column */ - margin-top: 16px; /* Add some space between the two rows */ + display: flex; + flex-direction: column; + margin-top: 16px; } - } \ No newline at end of file +} \ No newline at end of file diff --git a/screen-domain/src/components/Home/Card/Card.test.tsx b/screen-domain/src/components/Home/Card/Card.test.tsx index c527342..1805d88 100644 --- a/screen-domain/src/components/Home/Card/Card.test.tsx +++ b/screen-domain/src/components/Home/Card/Card.test.tsx @@ -5,10 +5,10 @@ import "../../../matchMedia.mock"; import { Card } from "./Card"; describe("Card Component Test", () => { - test("Display initial, name and icon", async () => { - // render(); + test("Display initial, name and icon", async () => { + // render(); - // expect(screen.getByText("Belgrano📍")).toBeVisible(); - // expect(screen.getByText("B")).toBeVisible(); - }); + // expect(screen.getByText("Belgrano📍")).toBeVisible(); + // expect(screen.getByText("B")).toBeVisible(); + }); }); diff --git a/screen-domain/src/components/Home/Card/Card.tsx b/screen-domain/src/components/Home/Card/Card.tsx index f191a42..6832893 100644 --- a/screen-domain/src/components/Home/Card/Card.tsx +++ b/screen-domain/src/components/Home/Card/Card.tsx @@ -1,101 +1,63 @@ -// import React from "react"; -// import { Avatar, Button } from "antd"; - -// interface FlightProps { -// flight_code: string; -// status: string; -// origin: string; -// destination: string; -// departure_time: string; -// arrival_time: string; -// gate: string; -// } - -// interface CardProps { -// flight: FlightProps; -// } - -// export const Card: React.FC = ({ -// flight: { flight_code, status, origin, destination, departure_time, arrival_time, gate }, -// }) => { -// console.log(flight_code) -// return ( -//
-// {flight_code.slice(0, 1).toUpperCase()} -//
-//
Name: {flight_code}
-//
Status: {status}
-//
Origin: {origin}
-//
Destination: {destination}
-//
Departure Time: {departure_time}
-//
Arrival Time: {arrival_time}
-//
Gate: {gate}
-//
-// 📍 -//
-// ); -// }; - import React from "react"; import { Avatar, Space, Typography, Tag } from "antd"; import { RightOutlined, ClockCircleOutlined, SwapOutlined, EnvironmentOutlined, CalendarOutlined } from "@ant-design/icons"; -import "./Card.css"; // Import a CSS file for styling, you can create this file with your styles +import "./Card.css"; interface FlightProps { - flight_code: string; - status: string; - origin: string; - destination: string; - departure_time: string; - arrival_time: string; - gate: string; + flight_code: string; + status: string; + origin: string; + destination: string; + departure_time: string; + arrival_time: string; + gate: string; } interface CardProps { - flight: FlightProps; + flight: FlightProps; } const { Text } = Typography; export const Card: React.FC = ({ flight }) => { - return ( -
- - } /> -
- {flight.flight_code} -
- - {flight.origin} {flight.destination} - -
+ return ( +
+ + } /> +
+ {flight.flight_code} +
+ + {flight.origin} {flight.destination} + +
+
+
+
+ + Status: + {flight.status} + + + Departure: + + + {flight.departure_time} + + + + Arrival: + + + {flight.arrival_time} + + + + Gate: + {flight.gate} + +
- -
- - Status: - {flight.status} - - - Departure: - - - {flight.departure_time} - - - - Arrival: - - - {flight.arrival_time} - - - - Gate: - {flight.gate} - -
-
- ); + ); }; diff --git a/screen-domain/src/components/Home/Departure.tsx b/screen-domain/src/components/Home/Departure.tsx new file mode 100644 index 0000000..4ad264d --- /dev/null +++ b/screen-domain/src/components/Home/Departure.tsx @@ -0,0 +1,82 @@ +import React, { useEffect, useState } from "react"; +import { Card } from "./Card/Card"; +import { useFetchOrigin } from "../../hooks/useFetchOrigin"; +import { Flight } from "../../Types"; +import './Home.css' +import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table'; +import 'react-super-responsive-table/dist/SuperResponsiveTableStyle.css'; + +interface Props { + flights?: Flight[]; +} + +export const Departure: React.FC = (props) => { + let origin = process.env.REACT_APP_ORIGIN; + + const { zones, error } = useFetchOrigin(origin); + const [startIndex, setStartIndex] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + if (zones.length <= 10) { + return; + } + setStartIndex((prevIndex) => (prevIndex + 10) >= zones.length ? 0 : (prevIndex + 10)); + }, 5000); + + return () => clearInterval(interval); + }, [zones]); + + + return ( +
+

Departure

+
+ + + + + + + + + + + + {zones.length > 0 && ( + <> + {zones.slice(startIndex, startIndex + 10).map((flight) => ( + + + + + + + + // ); + ))} + {startIndex + 10 >= zones.length && ( + <> + {Array.from({ length: startIndex + 10 - zones.length }).map((_, index) => { + return ( + + + + + + + + ) + })} + + ) + } + + )} + +
CodeTimeDestinationGateStatus
{flight.flight_code}{flight.departure_time}{flight.destination}{flight.gate}{flight.status}
+ {error ?
{error}
: <>} +
+
+ ); +}; diff --git a/screen-domain/src/components/Home/Home.css b/screen-domain/src/components/Home/Home.css new file mode 100644 index 0000000..fdacfa4 --- /dev/null +++ b/screen-domain/src/components/Home/Home.css @@ -0,0 +1,43 @@ +body { + font-family: 'Arial', sans-serif; + background-color: #f0f0f0; +} + +.App { + text-align: center; + margin-top: 20px; +} + +table { + width: 80%; + margin: 20px auto; + border-collapse: collapse; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + background-color: #fff; +} + +th, +td { + border: 1px solid #ddd; + padding: 10px; + text-align: left; +} + +th { + background-color: #4CAF50; + color: #fff; +} + +tbody tr:hover { + background-color: #f5f5f5; +} + +tfoot { + background-color: #4CAF50; + color: #fff; +} + +.delayed-flight { + background-color: #ffcccc; + color: #ff0000; +} \ No newline at end of file diff --git a/screen-domain/src/components/Home/Home.test.tsx b/screen-domain/src/components/Home/Home.test.tsx index b0940c0..881ea1d 100644 --- a/screen-domain/src/components/Home/Home.test.tsx +++ b/screen-domain/src/components/Home/Home.test.tsx @@ -1,28 +1,28 @@ const mockedUsedNavigate = jest.fn(); jest.mock("react-router-dom", () => ({ - ...jest.requireActual("react-router-dom"), - useNavigate: () => mockedUsedNavigate, + ...jest.requireActual("react-router-dom"), + useNavigate: () => mockedUsedNavigate, })); import "../../matchMedia.mock"; import "@testing-library/jest-dom"; import { render, screen } from "@testing-library/react"; -import { Home } from "./Home"; +// import { Home } from "./Departure"; describe("Home View Test", () => { - test("Display initial, name and icon", async () => { - // render( - // - // ); + test("Display initial, name and icon", async () => { + // render( + // + // ); - // expect(screen.getByText("Zones")).toBeVisible(); - // expect(screen.getByText("Belgrano📍")).toBeVisible(); - // expect(screen.getByText("San Isidro📍")).toBeVisible(); - }); + // expect(screen.getByText("Zones")).toBeVisible(); + // expect(screen.getByText("Belgrano📍")).toBeVisible(); + // expect(screen.getByText("San Isidro📍")).toBeVisible(); + }); }); diff --git a/screen-domain/src/components/Home/Home.tsx b/screen-domain/src/components/Home/Home.tsx index 4469ef3..5555a54 100644 --- a/screen-domain/src/components/Home/Home.tsx +++ b/screen-domain/src/components/Home/Home.tsx @@ -1,27 +1,24 @@ import React from "react"; -import { Card } from "./Card/Card"; -import { useFetchZones } from "../../hooks/useFetchZones"; import { Flight } from "../../Types"; +import './Home.css' +import { useNavigate } from "react-router"; +import './Page.css' interface Props { - flights?: Flight[]; + flights?: Flight[]; } export const Home: React.FC = (props) => { - // const urlParams = new URLSearchParams(window.location.search); - // const origin = urlParams.get('origin'); - // const { zones, error } = useFetchZones(origin); - const { zones, error } = useFetchZones(); + const navigate = useNavigate(); - return ( -
-

Flights

-
- {(props.flights ? props.flights : zones).map((u) => { - return ; - })} - {error ?
{error}
: <>} -
-
- ); + const submitHandler = (path: string) => { + navigate(path); + }; + + return ( +
+ + +
+ ); }; diff --git a/screen-domain/src/components/Home/Page.css b/screen-domain/src/components/Home/Page.css new file mode 100644 index 0000000..9f745c1 --- /dev/null +++ b/screen-domain/src/components/Home/Page.css @@ -0,0 +1,33 @@ +body { + font-family: 'Arial', sans-serif; + background-color: #f0f0f0; + margin: 0; + padding: 0; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +} + +div { + text-align: center; +} + +button { + background-color: #4CAF50; + border: none; + color: white; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + margin: 10px; + cursor: pointer; + border-radius: 5px; + transition: background-color 0.3s ease; +} + +button:hover { + background-color: #45a049; +} \ No newline at end of file diff --git a/screen-domain/src/db.ts b/screen-domain/src/db.ts index 9a63554..aaa63bd 100644 --- a/screen-domain/src/db.ts +++ b/screen-domain/src/db.ts @@ -3,123 +3,90 @@ let db: IDBDatabase; let version = 1; export enum Stores { - Flight = 'flights', + Departure = 'departure', + Arrival = 'arrival' } -interface EventTarget { - result: any -} +export const initDB = (): Promise => { + return new Promise((resolve) => { + request = indexedDB.open('myDB'); -export const initDB = (): Promise => { - return new Promise((resolve) => { - request = indexedDB.open('myDB'); + request.onupgradeneeded = (e) => { + let req = (e.target as IDBOpenDBRequest) + db = req.result; - request.onupgradeneeded = (e) => { - let req = (e.target as IDBOpenDBRequest) - db = req.result; + if (!db.objectStoreNames.contains(Stores.Arrival)) { + db.createObjectStore(Stores.Arrival, { keyPath: 'id' }); + } + if (!db.objectStoreNames.contains(Stores.Departure)) { + db.createObjectStore(Stores.Departure, { keyPath: 'id' }); + } + }; - if (!db.objectStoreNames.contains(Stores.Flight)) { - db.createObjectStore(Stores.Flight, { keyPath: 'id' }); - } - }; + request.onsuccess = (e) => { + let req = (e.target as IDBOpenDBRequest) + db = req.result; + version = db.version; + resolve(req.result); + }; - request.onsuccess = (e) => { - let req = (e.target as IDBOpenDBRequest) - db = req.result; - version = db.version; - resolve(req.result); - }; - - request.onerror = (e) => { - resolve(false); - }; - }); + request.onerror = (e) => { + resolve(false); + }; + }); }; -export const addData = (storeName: string, data: T): Promise => { - return new Promise((resolve) => { - request = indexedDB.open('myDB', version); - - request.onsuccess = (e) => { - let req = (e.target as IDBOpenDBRequest) - db = req.result; - const tx = db.transaction(storeName, 'readwrite'); - const store = tx.objectStore(storeName); - store.add(data); - resolve(data); - }; - - request.onerror = () => { - const error = request.error?.message - if (error) { - resolve(error); - } else { - resolve('Unknown error'); - } - }; - }); +export const addData = (storeName: string, data: T): Promise => { + return new Promise((resolve) => { + const tx = db.transaction(storeName, 'readwrite'); + const store = tx.objectStore(storeName); + store.add(data); + resolve(data); + }); }; export const deleteData = (storeName: string, key: number): Promise => { - return new Promise((resolve) => { - request = indexedDB.open('myDB', version); - - request.onsuccess = (e) => { - let req = (e.target as IDBOpenDBRequest) - db = req.result; - const tx = db.transaction(storeName, 'readwrite'); - const store = tx.objectStore(storeName); - const res = store.delete(key); - res.onsuccess = () => { - resolve(true); - }; - res.onerror = () => { - resolve(false); - } - }; - }); + return new Promise((resolve) => { + const tx = db.transaction(storeName, 'readwrite'); + const store = tx.objectStore(storeName); + const res = store.delete(key); + console.log("removing" + key) + res.onsuccess = () => { + console.log("success") + resolve(true); + }; + res.onerror = () => { + console.log("error") + resolve(false); + } + }); }; -export const updateData = (storeName: string, key: number, data: T): Promise => { - return new Promise((resolve) => { - request = indexedDB.open('myDB', version); - - request.onsuccess = (e) => { - let req = (e.target as IDBOpenDBRequest) - db = req.result; - const tx = db.transaction(storeName, 'readwrite'); - const store = tx.objectStore(storeName); - const res = store.get(key); - res.onsuccess = () => { - const newData = { ...res.result, ...data }; - store.put(newData); - resolve(newData); - }; - res.onerror = () => { - resolve(null); - } - }; - }); +export const updateData = (storeName: string, key: number, data: T): Promise => { + return new Promise((resolve) => { + const tx = db.transaction(storeName, 'readwrite'); + const store = tx.objectStore(storeName); + const res = store.get(key); + res.onsuccess = () => { + const newData = { ...res.result, ...data }; + store.put(newData); + resolve(newData); + }; + res.onerror = () => { + resolve(null); + } + }); }; -export const getStoreData = (storeName: Stores): Promise => { - return new Promise((resolve) => { - request = indexedDB.open('myDB'); - - request.onsuccess = (e) => { - let req = (e.target as IDBOpenDBRequest) - if (!req.result) { - resolve(null); - } - db = req.result; - const tx = db.transaction(storeName, 'readonly'); - const store = tx.objectStore(storeName); - const res = store.getAll(); - res.onsuccess = () => { - resolve(res.result); - }; - }; - }); +export const getStoreData = (storeName: Stores): Promise => { + return new Promise((resolve) => { + const tx = db.transaction(storeName, 'readonly'); + const store = tx.objectStore(storeName); + const res = store.getAll(); + res.onsuccess = () => { + resolve(res.result); + }; + }); }; -export {}; +export { }; diff --git a/screen-domain/src/hooks/useFetchDestination.tsx b/screen-domain/src/hooks/useFetchDestination.tsx new file mode 100644 index 0000000..bf37367 --- /dev/null +++ b/screen-domain/src/hooks/useFetchDestination.tsx @@ -0,0 +1,135 @@ +import { useEffect } from "react"; +import { useState } from "react"; +import { Flight } from "../Types"; +import { fetchZones } from "../Api"; +import { Stores, addData, deleteData, getStoreData, updateData, initDB } from '../db'; + +export const useFetchDestination = (destination: string | undefined) => { + const [error, setError] = useState(null); + const [zones, setZones] = useState([]); + + const isToday = (someDate: Date) => { + let today = new Date(); + return someDate.getDate() == today.getDate() && + someDate.getMonth() == today.getMonth() && + someDate.getFullYear() == today.getFullYear() + } + + useEffect(() => { + const intervalId = setInterval(() => { + let today = localStorage.getItem('date') + + if (today && !isToday(new Date(today))) { + getStoreData(Stores.Arrival) + .then((data) => { + if (data) { + data.map((u) => { + deleteData(Stores.Arrival, u.id) + }) + } + }) + localStorage.setItem('date', new Date().toString()) + } + + }, 36000) + + return () => clearInterval(intervalId); + }, []) + + + useEffect(() => { + setError(null); + let newUpdate = new Date().toISOString() + + localStorage.setItem('date', new Date().toString()) + + initDB().then((x) => { + console.log(x) + getStoreData(Stores.Arrival) + .then((data) => { + console.log(data) + if (data && data.length > 0) { + data.sort((a, b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime()) + setZones(data) + } else { + fetchZones(undefined, destination, null) + .then((data) => { + localStorage.setItem('lastUpdated', newUpdate) + let toAdd: Flight[] = [] + data.map((u) => { + if (u.status != 'Deleted') { + addData(Stores.Arrival, u) + toAdd.push(u) + } + }) + toAdd.sort((a, b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime()) + setZones(toAdd); + }) + .catch((error) => { }); + } + }) + } + ) + + }, [origin]); + + useEffect(() => { + const intervalId = setInterval(() => { + let lastUpdate = localStorage.getItem('lastUpdated') + let newUpdate = new Date().toISOString() + + fetchZones(undefined, destination, lastUpdate) + .then((data) => { + localStorage.setItem('lastUpdated', newUpdate) + let toAdd: Flight[] = [] + let toRemove: Flight[] = [] + + zones.forEach((c, i) => { + let index = data.findIndex(x => x.id === c.id) + if (index >= 0) { + console.log(data[index].departure_time + 'nuevo') + if (data[index].status == 'Deleted' || new Date(data[index].departure_time) < new Date()) { + console.log("sacamos") + toRemove.push(data[index]) + deleteData(Stores.Arrival, c.id) + } else { + toAdd.push(data[index]); + updateData(Stores.Arrival, c.id, data[index]) + } + } else { + console.log(new Date(c.departure_time)) + if (c.status == 'Deleted' || new Date(c.departure_time) < new Date()) { + console.log("sacamos?") + toRemove.push(c); + deleteData(Stores.Arrival, c.id) + } else { + toAdd.push(c); + } + } + }); + + let filtered = data.filter(o => + !toAdd.some(b => { return o.id === b.id }) && !toRemove.some(b => { return o.id === b.id }) && !(new Date(o.departure_time) < new Date()) + ) + const newArray = toAdd.concat(filtered); + console.log(filtered) + console.log(newArray) + filtered.forEach(c => { + addData(Stores.Arrival, c) + }) + + newArray.sort((a, b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime()) + setZones(newArray); + }) + .catch((error) => { + if (!!error.isAxiosError && !error.response) { + // + } + }); + }, 5000) + + return () => clearInterval(intervalId); + }, [origin, zones]) + + return { zones, error }; +}; diff --git a/screen-domain/src/hooks/useFetchOrigin.tsx b/screen-domain/src/hooks/useFetchOrigin.tsx new file mode 100644 index 0000000..3c8a3a4 --- /dev/null +++ b/screen-domain/src/hooks/useFetchOrigin.tsx @@ -0,0 +1,135 @@ +import { useEffect } from "react"; +import { useState } from "react"; +import { Flight } from "../Types"; +import { fetchZones } from "../Api"; +import { Stores, addData, deleteData, getStoreData, updateData, initDB } from '../db'; + +export const useFetchOrigin = (origin: string | undefined) => { + const [error, setError] = useState(null); + const [zones, setZones] = useState([]); + + const isToday = (someDate: Date) => { + let today = new Date(); + return someDate.getDate() == today.getDate() && + someDate.getMonth() == today.getMonth() && + someDate.getFullYear() == today.getFullYear() + } + + useEffect(() => { + const intervalId = setInterval(() => { + let today = localStorage.getItem('date') + + if (today && !isToday(new Date(today))) { + getStoreData(Stores.Departure) + .then((data) => { + if (data) { + data.map((u) => { + deleteData(Stores.Departure, u.id) + }) + } + }) + localStorage.setItem('date', new Date().toString()) + } + + }, 36000) + + return () => clearInterval(intervalId); + }, []) + + + useEffect(() => { + setError(null); + let newUpdate = new Date().toISOString() + + localStorage.setItem('date', new Date().toString()) + + initDB().then((x) => { + console.log(x) + getStoreData(Stores.Departure) + .then((data) => { + console.log(data) + if (data && data.length > 0) { + data.sort((a, b) => new Date(a.origin).getTime() - new Date(b.origin).getTime()) + setZones(data) + } else { + fetchZones(origin, undefined, null) + .then((data) => { + localStorage.setItem('lastUpdated', newUpdate) + let toAdd: Flight[] = [] + data.map((u) => { + if (u.status != 'Deleted') { + addData(Stores.Departure, u) + toAdd.push(u) + } + }) + toAdd.sort((a, b) => new Date(a.origin).getTime() - new Date(b.origin).getTime()) + setZones(toAdd); + }) + .catch((error) => { }); + } + }) + } + ) + + }, [origin]); + + useEffect(() => { + const intervalId = setInterval(() => { + let lastUpdate = localStorage.getItem('lastUpdated') + let newUpdate = new Date().toISOString() + + fetchZones(origin, undefined, lastUpdate) + .then((data) => { + localStorage.setItem('lastUpdated', newUpdate) + let toAdd: Flight[] = [] + let toRemove: Flight[] = [] + + zones.forEach((c, i) => { + let index = data.findIndex(x => x.id === c.id) + if (index >= 0) { + console.log(data[index].departure_time + 'nuevo') + if (data[index].status == 'Deleted' || new Date(data[index].departure_time) < new Date()) { + console.log("sacamos") + toRemove.push(data[index]) + deleteData(Stores.Departure, c.id) + } else { + toAdd.push(data[index]); + updateData(Stores.Departure, c.id, data[index]) + } + } else { + console.log(new Date(c.departure_time)) + if (c.status == 'Deleted' || new Date(c.departure_time) < new Date()) { + console.log("sacamos?") + toRemove.push(c); + deleteData(Stores.Departure, c.id) + } else { + toAdd.push(c); + } + } + }); + + let filtered = data.filter(o => + !toAdd.some(b => { return o.id === b.id }) && !toRemove.some(b => { return o.id === b.id }) && !(new Date(o.departure_time) < new Date()) + ) + const newArray = toAdd.concat(filtered); + console.log(filtered) + console.log(newArray) + filtered.forEach(c => { + addData(Stores.Departure, c) + }) + + newArray.sort((a, b) => new Date(a.origin).getTime() - new Date(b.origin).getTime()) + setZones(newArray); + }) + .catch((error) => { + if (!!error.isAxiosError && !error.response) { + // + } + }); + }, 5000) + + return () => clearInterval(intervalId); + }, [origin, zones]) + + return { zones, error }; +}; diff --git a/screen-domain/src/hooks/useFetchZones.tsx b/screen-domain/src/hooks/useFetchZones.tsx deleted file mode 100644 index 73cf036..0000000 --- a/screen-domain/src/hooks/useFetchZones.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React, { useEffect } from "react"; -import { useState } from "react"; -import { User, Flight } from "../Types"; -import { fetchZones } from "../Api"; -import { Stores, addData, deleteData, getStoreData, updateData, initDB } from '../db'; - -export const useFetchZones = () => { - const [error, setError] = useState(null); - const [zones, setZones] = useState([]); - - let origin = process.env.REACT_APP_ORIGIN; - - useEffect(() => { - setError(null); - let newUpdate = new Date().toISOString() - - getStoreData(Stores.Flight) - .then((data) => { - console.log(data) - if (data && data.length > 0) { - setZones(data) - } else { - fetchZones(origin, null) - .then((data) => { - localStorage.setItem('lastUpdated', newUpdate) - let toAdd: Flight[] = [] - data.map((u) => { - if (u.status != 'Deleted') { - addData(Stores.Flight, u) - toAdd.push(u) - } - }) - setZones(toAdd); - }) - .catch((error) => {}); - } - }) - - }, [origin]); - - useEffect(() => { - const intervalId = setInterval(() => { - let lastUpdate = localStorage.getItem('lastUpdated') - let newUpdate = new Date().toISOString() - - fetchZones(origin, lastUpdate) - .then((data) => { - localStorage.setItem('lastUpdated', newUpdate) - let toAdd: Flight[] = [] - let toRemove: Flight[] = [] - - zones.forEach((c, i) => { - let index = data.findIndex(x => x.id === c.id) - if (index >= 0) { - console.log(data[index].status) - if (data[index].status == 'Deleted') { - console.log("sacamos") - toRemove.push(data[index]) - deleteData(Stores.Flight, c.id) - } else { - toAdd.push(data[index]); - updateData(Stores.Flight, c.id, data[index]) - } - } else { - if (c.status == 'Deleted') { - toRemove.push(c); - } else { - toAdd.push(c); - } - } - }); - - console.log(toAdd) - console.log(toRemove) - let filtered = data.filter(o => !toAdd.some(b => { return o.id === b.id}) && !toRemove.some(b => { return o.id === b.id})) - const newArray = toAdd.concat(filtered); - filtered.forEach(c => { - addData(Stores.Flight, c) - }) - - setZones(newArray); - }) - .catch((error) => {}); - }, 5000) - - return () => clearInterval(intervalId); - }, [origin, zones]) - - return { zones, error }; -}; diff --git a/screen-domain/src/hooks/useIsConnected.tsx b/screen-domain/src/hooks/useIsConnected.tsx index 3ec39fc..429e5b1 100644 --- a/screen-domain/src/hooks/useIsConnected.tsx +++ b/screen-domain/src/hooks/useIsConnected.tsx @@ -3,25 +3,29 @@ import { useState } from "react"; import { ping } from "../Api"; export const useIsConnected = () => { - const [connected, setConnected] = useState(false); + const [connected, setConnected] = useState(true); - useEffect(() => { - ping() - .then(() => { - setConnected(true); - }) - .catch(() => { - setConnected(false); - }); - }, []); + useEffect(() => { + const interval = setInterval(() => { + ping() + .then(() => { + setConnected(true); + }) + .catch(() => { + setConnected(false); + }); + }, 5000); - return ( -
- {connected ? ( -

Connected

- ) : ( -

Disconnected

- )} -
- ); + return () => clearInterval(interval); + }, []); + + return ( +
+ {connected ? ( +

Connected

+ ) : ( +

Disconnected

+ )} +
+ ); }; diff --git a/screen-domain/src/index.css b/screen-domain/src/index.css index bd54b3b..a94edcd 100644 --- a/screen-domain/src/index.css +++ b/screen-domain/src/index.css @@ -1,106 +1,106 @@ body { - margin: 0; - font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", - "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", - "Helvetica Neue", sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + margin: 0; + font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", + "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", - monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; } .App { - width: 100vw; - height: 100vh; - display: flex; - justify-content: center; - align-items: center; - background-color: #eff2f7; + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background-color: #eff2f7; } .Box { - border-radius: 20px; - box-shadow: 0px 20px 60px rgba(0, 0, 0, 0.2); - padding: 50px; - gap: 30px; - background-color: white; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; + border-radius: 20px; + box-shadow: 0px 20px 60px rgba(0, 0, 0, 0.2); + padding: 50px; + gap: 30px; + background-color: white; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; } .Small { - width: 250px; - height: 400px; + width: 250px; + height: 400px; } .Section { - flex: 1; - width: 100%; - padding: 30px 50px; - gap: 30px; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; + flex: 1; + width: 100%; + padding: 30px 50px; + gap: 30px; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; } .Image { - width: 150px; + width: 150px; } .Connected { - color: green; + color: green; } .Disconnected { - color: red; + color: red; } .FloatingStatus { - position: absolute; - top: 10px; - right: 50px; + position: absolute; + top: 10px; + right: 50px; } .LogoutButton { - position: absolute; - bottom: 10px; - right: 50px; + position: absolute; + bottom: 10px; + right: 50px; } .Card { - border-radius: 8px; - box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.2); - gap: 10px; - padding: 10px; - width: 100%; - background-color: white; - display: flex; - align-items: center; + border-radius: 8px; + box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.2); + gap: 10px; + padding: 10px; + width: 100%; + background-color: white; + display: flex; + align-items: center; } .Items { - height: 100%; - width: 100%; - display: flex; + height: 100%; + width: 100%; + display: flex; flex-wrap: wrap; - justify-content: space-between; - align-items: center; + justify-content: space-between; + align-items: center; gap: 20px; } .List { - width: 100%; - height: 500px; - gap: 30px; - padding: 20px; - overflow-y: auto; - display: flex; - align-items: center; - flex-direction: column; -} + width: 100%; + height: 500px; + gap: 30px; + padding: 20px; + overflow-y: auto; + display: flex; + align-items: center; + flex-direction: column; +} \ No newline at end of file diff --git a/screen-domain/src/tests/integration/flights.spec.js b/screen-domain/src/tests/integration/flights.spec.js new file mode 100644 index 0000000..c9b21c1 --- /dev/null +++ b/screen-domain/src/tests/integration/flights.spec.js @@ -0,0 +1,77 @@ +const request = require('supertest'); +const app = 'http://127.0.0.1:5000' + +const authCredentials = { + email: 'info@lufthansa.com', + password: 'password1234', +}; + +describe('Flight API Endpoints', () => { + let authToken; + + beforeAll(async () => { + const loginResponse = await request(app) + .post('/auth/login') + .send(authCredentials); + + expect(loginResponse.statusCode).toBe(200); + expect(loginResponse.body).toHaveProperty('access_token'); + authToken = loginResponse.body.access_token; + }); + + it('should create a new flight', async () => { + const newFlight = { + "flight_code": "ABC127", + "status": "En ruta", + "origin": "Frankfurt", + "destination": "Ciudad A", + "departure_time": "2023-10-29 09:57 PM", + "arrival_time": "2023-10-30 12:00 PM", + "gate": "A2" + }; + + const response = await request(app) + .post('/flights') + .send(newFlight) + .set('Authorization', `Bearer ${authToken}`); + + expect(response.statusCode).toBe(200); + expect(response.body).toHaveProperty('id'); + createdFlightId = response.body.id; + }); + + it('should retrieve the created flight by ID', async () => { + const response = await request(app) + .get(`/flights/${createdFlightId}`) + .set('Authorization', `Bearer ${authToken}`); + + expect(response.statusCode).toBe(200); + expect(response.body).toHaveProperty('id', createdFlightId); + }); + + it('should update a flight by ID', async () => { + const flightUpdate = { + "status": "Delayed" + }; + + const response = await request(app) + .patch(`/flights/${createdFlightId}`) + .send(flightUpdate) + .set('Authorization', `Bearer ${authToken}`); + + expect(response.statusCode).toBe(200); + expect(response.body).toHaveProperty('id'); + expect(response.body.status).toBe('Delayed'); + }); + + it('should retrieve filtered flights', async () => { + const response = await request(app) + .get('/flights') + .query({ origin: 'Frankfurt' }) + .set('Authorization', `Bearer ${authToken}`); + + expect(response.statusCode).toBe(200); + expect(response.body).toBeInstanceOf(Array); + expect(response.body[0].id).toBe(createdFlightId) + }); +}); \ No newline at end of file diff --git a/screen-domain/test.sh b/screen-domain/test.sh index c20a79c..57664cd 100644 --- a/screen-domain/test.sh +++ b/screen-domain/test.sh @@ -1,8 +1,7 @@ #!/bin/bash -curl -X DELETE api:5000/ping -curl -X POST api:5000/ping - - -# npm test -echo "NPM TEST" +if [ "${TEST_TARGET:-}" = "INTEGRATION" ]; then + npm run test:integration +else + npm run test +fi