From 6486eea6a416e65ec22d00247e4e3cab61f18a4d Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Fri, 24 Jun 2022 10:41:32 +0300 Subject: [PATCH 1/4] CI --- .github/workflows/api-tests.yml | 192 ++- .github/workflows/build.sh | 4 + .github/workflows/dashboard-template.hbs | 1672 ++++++++++++++++++++++ .github/workflows/run.sh | 3 + .github/workflows/wait-for-it.sh | 183 +++ 5 files changed, 2052 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/build.sh create mode 100644 .github/workflows/dashboard-template.hbs create mode 100644 .github/workflows/run.sh create mode 100644 .github/workflows/wait-for-it.sh diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml index 51c3d8b3..c90846c0 100644 --- a/.github/workflows/api-tests.yml +++ b/.github/workflows/api-tests.yml @@ -2,7 +2,195 @@ name: ShareIt API Tests on: pull_request: + workflow_call: jobs: - build: - uses: yandex-praktikum/java-shareit/.github/workflows/api-tests.yml@ci \ No newline at end of file + check-repo: + runs-on: ubuntu-latest + + steps: + - name: Check repo not fork and public + run: | + REPO=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" "/repos/${GITHUB_REPOSITORY}") + FORK=$(jq '.fork' <<< "$REPO") + PRIVATE=$(jq '.private' <<< "$REPO") + echo "FORK='$FORK', PRIVATE='$PRIVATE', GITHUB_REPOSITORY_OWNER=${GITHUB_REPOSITORY_OWNER} " + if [[ "$FORK" == "true" ]] + then + echo "Use the repository automatically created by Yandex Practicum (works in fork repositories are not accepted)" + echo "Используйте только репозиторий созданный Yandex Practicum, работы в форк репозитории не принимаются" + exit -1 + fi + if [[ "$GITHUB_REPOSITORY_OWNER" == "yandex-praktikum" ]] + then + echo "Use the repository automatically created by Yandex Practicum (works in fork repositories are not accepted)" + echo "Используйте только репозиторий созданный Yandex Practicum, работы в форк репозитории не принимаются" + exit -2 + fi + if [[ "$PRIVATE" == "true" && "$GITHUB_REPOSITORY_OWNER" != "praktikum-java" ]] + then + echo "Share your repository, make it public" + echo "Откройте доступ к вашему репозиторию, сделайте его публичным" + exit -3 + fi + env: + GH_TOKEN: ${{ github.token }} + + - name: Check branch name + run: | + if [[ "$GITHUB_BASE_REF" != "main" && "$GITHUB_REPOSITORY_OWNER" != "praktikum-java" ]] + then + echo "Set the pull request to merge branch 'main' (instead of '$GITHUB_BASE_REF')" + echo "Задайте в Pull request ветку слияния 'main' (вместо '$GITHUB_BASE_REF')" + exit -2 + fi + echo "Github target '$GITHUB_BASE_REF' - OK" + + if [[ "$GITHUB_HEAD_REF" == "add-controllers" ]] + then + echo "Sprint 14: add-controllers - OK" + exit + fi + if [[ "$GITHUB_HEAD_REF" == "add-bookings" ]] + then + echo "Sprint 15: add-bookings - OK" + PULL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/${GITHUB_REPOSITORY}/pulls?head=${GITHUB_REPOSITORY_OWNER}:add-controllers || true) + OPEN=$(jq '. | length' <<< "$PULL") + if [[ "$OPEN" != "0" && "$GITHUB_REPOSITORY_OWNER" != "praktikum-java" ]] + then + PULL_URL=$(jq '.[0].html_url' <<< "$PULL") + echo "Merge the add-controllers branch pull request: ${PULL_URL}" + echo "Объедините pull request ветки add-controllers: ${PULL_URL}" + exit -3 + fi + echo "Sprint 14: add-controllers - Merged" + exit + fi + if [[ "$GITHUB_HEAD_REF" == "add-item-requests-and-gateway" ]] + then + echo "Sprint 16: add-item-requests-and-gateway - OK" + PULL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/${GITHUB_REPOSITORY}/pulls?head=${GITHUB_REPOSITORY_OWNER}:add-bookings || true) + OPEN=$(jq '. | length' <<< "$PULL") + if [[ "$OPEN" != "0" && "$GITHUB_REPOSITORY_OWNER" != "praktikum-java" ]] + then + PULL_URL=$(jq '.[0].html_url' <<< "$PULL") + echo "Merge the add-bookings branch pull request: ${PULL_URL}" + echo "Объедините pull request ветки add-bookings: ${PULL_URL}" + exit -4 + fi + echo "Sprint 15: add-bookings - Merged" + exit + fi + echo "Correct branch name '$GITHUB_HEAD_REF' according to the spec, allowed: add-controllers, add-bookings, add-item-requests-and-gateway" + echo "Исправьте пожалуйста имя ветки '$GITHUB_HEAD_REF' согласно заданию, разрешены: add-controllers, add-bookings, add-item-requests-and-gateway" + exit -1 + env: + GH_TOKEN: ${{ github.token }} + + - name: Check files + run: | + PULL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/${GITHUB_REPOSITORY}/pulls/${PULL_NUMBER}/files?per_page=100 || true) + FILENAMES=$(jq '.[] | .filename' <<< "$PULL") + if [[ "$FILENAMES" =~ "api-tests.yml" ]] + then + echo "The pull request contains the api-tests.yml file and cannot be modified. Remove it from PR" + echo "Pull request содержит файл api-tests.yml, его изменять нельзя. Удалите его из PR" + exit -1 + fi + if [[ "$FILENAMES" =~ "checkstyle.xml" ]] + then + echo "The pull request contains the checkstyle.xml file and cannot be modified. Remove it from PR" + echo "Pull request содержит файл checkstyle.xml, его изменять нельзя. Удалите его из PR" + exit -2 + fi + if [[ "$FILENAMES" =~ ".class" ]] || [[ "$FILENAMES" =~ ".jar" ]] || [[ "$FILENAMES" =~ "mvn" ]] || [[ "$FILENAMES" =~ ".DS_Store" ]] \ + || [[ "$FILENAMES" =~ ".idea" ]] || [[ "$FILENAMES" =~ ".iws" ]] || [[ "$FILENAMES" =~ ".iml" ]] || [[ "$FILENAMES" =~ ".ipr" ]] \ + || [[ "$FILENAMES" =~ ".db" ]] || [[ "$FILENAMES" =~ ".log" ]] || [[ "$FILENAMES" =~ "target/" ]] + then + echo "The pull request contains the binary files. Remove them (*.class, *.jar, *.DS_Store ...) from PR" + echo "Pull request содержит двоичные файлы. Удалите их (*.class, *.jar, *.DS_Store ...) из PR" + exit -3 + fi + echo "PR files - OK" + exit + env: + PULL_NUMBER: ${{ github.event.number }} + GH_TOKEN: ${{ github.token }} + + build-shareit: + + needs: check-repo + runs-on: ubuntu-latest + + steps: + - name: Checkout target repo + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: maven + + - name: Checkout tests + uses: actions/checkout@v4 + with: + repository: 'yandex-praktikum/java-shareit' + ref: ${{ github.event.pull_request.head.ref }} + path: tests + + - name: Check and Build application + run: | + chmod a+x ./tests/.github/workflows/build.sh + ./tests/.github/workflows/build.sh + + - name: Install Node + uses: actions/setup-node@v4 + with: + node-version: '16.x' + + - name: Install newman + run: | + npm install -g newman + npm install -g newman-reporter-htmlextra + + - name: Run Application + run: | + chmod a+x ./tests/.github/workflows/run.sh + ./tests/.github/workflows/run.sh + + - name: Run POSTMAN tests + run: > + newman run ./tests/postman/sprint.json + --delay-request 50 -r cli,htmlextra + --verbose --color on --reporter-htmlextra-darkTheme + --reporter-htmlextra-export reports/shareIt.html + --reporter-htmlextra-title "Отчет по тестам" + --reporter-htmlextra-logs true + --reporter-htmlextra-template ./tests/.github/workflows/dashboard-template.hbs + + - name: Compose logs + if: ${{ failure() }} + run: | + if test -f "docker-compose.yml"; then + docker compose -f docker-compose.yml ps + docker compose -f docker-compose.yml stop + docker compose -f docker-compose.yml logs -f gateway >> ./gateway.log || true + docker compose -f docker-compose.yml logs -f server >> ./server.log || true + fi + + + - name: Upload log artifact + if: ${{ failure() }} + uses: actions/upload-artifact@v4 + with: + name: shareIt_log + path: ./*.log + + - name: Archive artifacts + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: postman_tests_report + path: reports diff --git a/.github/workflows/build.sh b/.github/workflows/build.sh new file mode 100644 index 00000000..e8d98050 --- /dev/null +++ b/.github/workflows/build.sh @@ -0,0 +1,4 @@ +cp -rf ./tests/checkstyle.xml ./checkstyle.xml +cp -rf ./tests/suppressions.xml ./suppressions.xml +mvn enforcer:enforce -Denforcer.rules=requireProfileIdsExist -P check --no-transfer-progress +mvn verify -P check --no-transfer-progress \ No newline at end of file diff --git a/.github/workflows/dashboard-template.hbs b/.github/workflows/dashboard-template.hbs new file mode 100644 index 00000000..56aa1638 --- /dev/null +++ b/.github/workflows/dashboard-template.hbs @@ -0,0 +1,1672 @@ + + + + + {{browserTitle}} + + + + + + + + + +
+
+ + + +
+ {{#with summary}} +
+
+ + {{/with}} +
+
+
+
+

{{title}}

+
{{timestamp}}
+{{#with summary}} +
+
+
+
+
+ +
+
Итого итераций
+

{{stats.iterations.total}}

+
+
+
+
+
+
+
+ +
+
Всего проверенных утверждений
+

{{totalTests stats.assertions.total skippedTests.length}}

+
+
+
+
+
+
+
+ +
+
Всего проваленных тестов
+

{{failures.length}}

+
+
+
+
+
+
+
+ +
+
Всего пропущено тестов
+

{{#gt skippedTests.length 0}}{{skippedTests.length}}{{else}}0{{/gt}}

+
+
+
+
+
+
+
+
+
+
+
+
File Information
+ Коллекция: {{collection.name}}
+ {{/with}} + {{#if folders}} Указанные папки: {{folders}}
{{/if}} + {{#with summary}} + {{#if environment.name}} Окружение: {{environment.name}}
{{/if}} +
+
+
+
+ {{#if @root.showGlobalData}} + {{#if globals.values.members.length}} +
+
+
+
+
+ +
+
+
+ +
+ + + + {{#each globals.values.members}} + {{#isNotIn key @root.skipGlobalVars}} + + + + + {{/isNotIn}} + {{/each}} + +
Название переменнойЗначение переменной
{{key}}{{value}}
+
+
+
+
+
+
+
+ {{/if}} + {{/if}} + {{#if @root.showEnvironmentData}} + {{#if environment.values.members.length}} +
+
+
+
+
+ +
+
+
+ +
+ + + + {{#each environment.values.members}} + {{#isNotIn key @root.skipEnvironmentVars}} + + + + + {{/isNotIn}} + {{/each}} + +
Название переменнойЗначение переменной
{{key}}{{value}}
+
+
+
+
+
+
+
+ {{/if}} + {{/if}} + {{#if collection.description}} +
+
+
+
+
Описание Коллекции
+
+ {{collection.description}} +
+
+
+
+
+ {{/if}} +
+
+
+
+
Временные рамки и данные
+ Общая длительность выполнения: {{duration}}
+ Всего данных получено: {{responseTotal}}
+ Среднее время отклика: {{responseAverage}}
+
+
+
+
+ {{/with}} +
+
+
+ + + + + + + + + + {{#with summary.stats}} + + + + + + + + + + + + + + + + {{/with}} + {{#with summary}} + + + + + + + + + + + {{/with}} + +
Элемент сводных данныхВсегоПровалено
Requests{{requests.total}}{{requests.failed}}
Prerequest Scripts{{prerequestScripts.total}}{{prerequestScripts.failed}}
Test Scripts{{testScripts.total}}{{testScripts.failed}}
Assertions{{totalTests stats.assertions.total skippedTests.length}}{{stats.assertions.failed}}
Skipped Tests{{#gt skippedTests.length 0}}{{skippedTests.length}}{{else}}0{{/gt}}-
+
+
+
+
+
+
+
+
+
+
+ + + {{#if summary.failures.length}} +
+ +
+
+
+ + {{#with summary}} +
+

Showing {{failures.length}} {{#gt failures.length 1}}Failures{{else}}Failure{{/gt}}

+
+ {{/with}} + {{#each summary.failures}} +
+
+
+ +
+
+
Failed Test: {{error.test}}
+
+
Assertion Error Message
+
+
{{error.message}}
+
+
+
+
+
+
+ {{/each}} + {{else}} +
+

There are no failed tests



+
+ {{/if}} +
+ +
+ + + {{#if summary.skippedTests.length}} +
+ +
+
+
+ + {{#with summary}} +
+

Showing {{skippedTests.length}} Skipped {{#gt skippedTests.length 1}}Tests{{else}}Test{{/gt}}

+
+ {{/with}} + {{#each summary.skippedTests}} +
+
+
+ +
+
+
Request Name: {{item.name}}
+
+
+
+
+
+ {{/each}} + {{else}} +
+

There are no skipped tests



+
+ {{/if}} +
+
+ + + +
+ {{#if summary.failures.length}} + + {{/if}} + + +
+ +
+ {{#with summary}} +
{{stats.iterations.total}} {{#gt stats.iterations.total 1}}Iterations{{else}}Iteration{{/gt}} available to view
+ {{#gt stats.iterations.total 18}}{{/gt}} + {{/with}} + +
+
+
+{{#each aggregations}} + {{#isNotIn parent.name @root.skipFolders}} + {{#if parent.name }} + +
+ {{> aggregations}} +
+ {{else}} + {{> aggregations}} + {{/if}} + {{/isNotIn}} +{{/each}} +
+
+
+
+
+
+ +{{#*inline "aggregations"}} +{{#isNotIn parent.name @root.skipFolders}} +{{#if @root.showFolderDescription}} +{{#if parent.description.content}} + +
+
+
+
+
+
Описание папки
+
+ {{parent.description.content}} +
+
+
+
+
+
+{{/if}} +{{/if}} +{{#each executions}} +{{#isNotIn item.name @root.skipRequests}} +
+
+
+
+
+ + {{#if cumulativeTests.skipped}} + {{cumulativeTests.skipped}} Пропущено {{#gt cumulativeTests.skipped 1}}Тестов{{else}}Тест{{/gt}} + {{/if}} +
+
+
+ {{#with request}} + {{#if description.content}} +
+
+
+
+
+
Описание запроса
+
+ {{description.content}} +
+
+
+
+
+
+ {{/if}} + {{/with}} +
+
+
+
+
+
Информация о запросе
+ HTTP-метод запроса: {{request.method}}
+ URL запроса: {{request.url}}
+
+
+
+
+
Информация об ответе
+ Код статуса ответа: {{response.code}} - {{response.status}}
+ Среднее время на запрос: {{mean.time}}
+ Средний размер одного запроса: {{mean.size}}
+
+
Процент прохождения тестов
+
+ {{#if assertions.length}} +
+
+
{{#gte cumulativeTests.passed 1}}{{percent cumulativeTests.passed cumulativeTests.failed}} %{{else}}0 %{{/gte}}
+
+
+ {{else}} +
+
+
Для данного запроса нет тестов
+
+
+ {{/if}} +
+
+
+
+
+
+ {{#with request}} + {{#unless @root.omitHeaders}} + {{#unless @root.skipSensitiveData}} +
+
+
+
+
+
Заголовки запроса
+ {{#if headers}} +
+ + + + {{#each headers.members}} + {{#isNotIn key @root.skipHeaders}} + + + + + {{/isNotIn}} + {{/each}} + +
Название заголовкаЗначение заголовка
{{key}}{{value}}
+
+ {{/if}} +
+
+
+
+
+ {{/unless}} + {{/unless}} + {{/with}} + {{#unless @root.skipSensitiveData}} + {{#unless @root.omitRequestBodies}} + {{#isNotIn item.name @root.hideRequestBody}} + {{#with request}} + {{#if body.raw}} +
+
+
+
+
+
Тело запроса
+
+
{{body.raw}}
+
+ +
+
+
+
+
+ {{/if}} + {{/with}} + {{/isNotIn}} + {{/unless}} + {{/unless}} + {{#unless @root.skipSensitiveData}} + {{#unless @root.omitRequestBodies}} + {{#isNotIn item.name @root.hideRequestBody}} + {{#with request}} + {{#if body.formdata.members}} +
+
+
+
+
+
Тело Запроса
+
+
{{formdata body.formdata.members}}
+
+ +
+
+
+
+
+ {{/if}} + {{/with}} + {{/isNotIn}} + {{/unless}} + {{/unless}} + {{#unless @root.skipSensitiveData}} + {{#unless @root.omitRequestBodies}} + {{#isNotIn item.name @root.hideRequestBody}} + {{#with request}} + {{#if body.urlencoded.members}} +
+
+
+
+
+
Тело Запроса
+
+
{{formdata body.urlencoded.members}}
+
+ +
+
+
+
+
+ {{/if}} + {{/with}} + {{/isNotIn}} + {{/unless}} + {{/unless}} + {{#unless @root.skipSensitiveData}} + {{#unless @root.omitRequestBodies}} + {{#isNotIn item.name @root.hideRequestBody}} + {{#with request}} + {{#if body.graphql}} +
+
+
+
+
+
Тело Запроса
+
+
{{body.graphql.query}}
+
+ + {{#if body.graphql.variables }} +
Graphql Variables
+
+
{{body.graphql.variables}}
+
+ {{/if}} +
+
+
+
+
+ {{/if}} + {{/with}} + {{/isNotIn}} + {{/unless}} + {{/unless}} + {{#unless @root.omitHeaders}} + {{#unless @root.skipSensitiveData}} +
+
+
+
+
+
Заголовки Ответа
+ {{#if response.header}} +
+ + + + {{#each response.header}} + {{#isNotIn key @root.skipHeaders}} + + + + + {{/isNotIn}} + {{/each}} + +
Название заголовкаЗначение заголовка
{{key}}{{value}}
+
+ {{/if}} +
+
+
+
+
+ {{/unless}} + {{/unless}} + {{#unless @root.skipSensitiveData}} + {{#unless @root.omitResponseBodies}} + {{#isNotIn item.name @root.hideResponseBody}} +
+
+
+
+
+
Тело Ответа
+ {{#if response.body}} +
+
{{response.body}}
+
+ + {{else}} +
У ответа на этот запрос нет тела
+ {{/if}} +
+
+
+
+
+ {{/isNotIn}} + {{/unless}} + {{/unless}} + {{#if consoleLogs.length}} +
+
+
+
+
+
Логи консоли
+
+
+ + + + {{#each consoleLogs}} + + + + {{/each}} + +
Залогированные сообщения
{{#each messages}}{{this}}{{/each}}
+
+
+
+
+
+
+
+ {{/if}} +
+
+
+
Информация о тесте
+ {{#if assertions.length}} +
+ + + + {{#each assertions}} + + + + + + + {{/each}} + + + + + + + + + +
НазваниеПройденоПроваленоПропущено
{{this.name}}{{this.passed}}{{this.failed}}{{this.skipped}}
Всего{{cumulativeTests.passed}}{{cumulativeTests.failed}}{{cumulativeTests.skipped}}
+
+
+
+
+
+
+
{{#lte cumulativeTests.failed 1}}Проваленный тест{{else}}Проваленные тесты{{/lte}}
+
+ + + + {{#each assertions}} + {{#isTheSame testFailure.test this.name}} + + + + + {{/isTheSame}} + {{/each}} + +
Название тестаОшибка проверки
{{testFailure.test}}
{{testFailure.message}}
+
+
+
+
+
+
+ {{else}} +
No Tests for this request
+ {{/if}} +
+
+
+
+
+
+
+
+
+{{/isNotIn}} +{{/each}} +{{/isNotIn}} +{{/inline}} + + + + + + +{{#eq noSyntaxHighlighting false}} + + +{{/eq}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/workflows/run.sh b/.github/workflows/run.sh new file mode 100644 index 00000000..1d46b04a --- /dev/null +++ b/.github/workflows/run.sh @@ -0,0 +1,3 @@ +nohup mvn spring-boot:run >> console.log 2>&1 & +chmod a+x ./tests/.github/workflows/wait-for-it.sh +./tests/.github/workflows/wait-for-it.sh -t 60 localhost:8080 \ No newline at end of file diff --git a/.github/workflows/wait-for-it.sh b/.github/workflows/wait-for-it.sh new file mode 100644 index 00000000..859de3f2 --- /dev/null +++ b/.github/workflows/wait-for-it.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else +# (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + (curl --fail --silent $WAITFORIT_HOST:$WAITFORIT_PORT/actuator/health | grep UP) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi \ No newline at end of file From c4d084241152c2cd89be1eec713694a6d7eeea7c Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Fri, 24 Jun 2022 10:41:32 +0300 Subject: [PATCH 2/4] #1 Sprint 14 add-controllers --- .github/workflows/build.sh | 2 +- postman/sprint.json | 2725 ++++++++++++++++++++++++++++++++++++ 2 files changed, 2726 insertions(+), 1 deletion(-) create mode 100644 postman/sprint.json diff --git a/.github/workflows/build.sh b/.github/workflows/build.sh index e8d98050..2f047fab 100644 --- a/.github/workflows/build.sh +++ b/.github/workflows/build.sh @@ -1,4 +1,4 @@ cp -rf ./tests/checkstyle.xml ./checkstyle.xml cp -rf ./tests/suppressions.xml ./suppressions.xml -mvn enforcer:enforce -Denforcer.rules=requireProfileIdsExist -P check --no-transfer-progress +mvn enforcer:enforce -Denforcer.rules=requireProfileIdsExist -P check --no-transfer-progress && mvn verify -P check --no-transfer-progress \ No newline at end of file diff --git a/postman/sprint.json b/postman/sprint.json new file mode 100644 index 00000000..a3f02fac --- /dev/null +++ b/postman/sprint.json @@ -0,0 +1,2725 @@ +{ + "info": { + "_postman_id": "f4a82602-e802-4363-87b5-c1814ccd49db", + "name": "Sprint 14 ShareIt (add-controllers)", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "23073145", + "_collection_link": "https://universal-shadow-295426.postman.co/workspace/My-Workspace~4200f6aa-0504-44b1-8a1d-707d0dcbd5ce/collection/13708500-f4a82602-e802-4363-87b5-c1814ccd49db?action=share&source=collection_link&creator=23073145" + }, + "item": [ + { + "name": "users", + "item": [ + { + "name": "Create user", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " pm.collectionVariables.set(\"userName\", user.name);\r", + " pm.collectionVariables.set(\"userEmail\", user.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200 or 201\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([200,201]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{userName}}\",\n \"email\": \"{{userEmail}}\"\n}" + }, + "url": { + "raw": "localhost:8080/users", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "users" + ] + } + }, + "response": [] + }, + { + "name": "Create user without email", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user2 = rnd.getUser();\r", + " pm.collectionVariables.set(\"userName\", user2.name);\r", + " pm.collectionVariables.set(\"userEmail\", user2.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{userName}}\"\n}" + }, + "url": { + "raw": "localhost:8080/users", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "users" + ] + } + }, + "response": [] + }, + { + "name": "Create 2 users with same email", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user1 = rnd.getUser();\r", + " us = await api.addUser(user1);\r", + " user2 = rnd.getUser();\r", + " user2.email = user1.email;\r", + " pm.collectionVariables.set(\"userName\", user2.name);\r", + " pm.collectionVariables.set(\"userEmail\", user2.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 409\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([409, 500]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{userName}}\",\n \"email\": \"{{userEmail}}\"\n}" + }, + "url": { + "raw": "localhost:8080/users", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "users" + ] + } + }, + "response": [] + }, + { + "name": "Create user with invalid email", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " pm.collectionVariables.set(\"userName\", user.name);\r", + " pm.collectionVariables.set(\"userEmail\", user.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{userName}}\",\n \"email\": \"user.com\"\n}" + }, + "url": { + "raw": "localhost:8080/users", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "users" + ] + } + }, + "response": [] + }, + { + "name": "User update", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " us = await api.addUser(user);\r", + " pm.collectionVariables.set(\"userId\", us.id);\r", + " us = rnd.getUser()\r", + " pm.collectionVariables.set(\"userName\", us.name);\r", + " pm.collectionVariables.set(\"userEmail\", us.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.be.ok;\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "pm.test(\"Test user 'id' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('id');\r", + " var id = pm.collectionVariables.get(\"userId\");\r", + " pm.expect(jsonData.id, '\"id\" must be ' + id).to.eql(Number(id));\r", + "});\r", + "pm.test(\"Test user 'email' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('email');\r", + " var email = pm.collectionVariables.get(\"userEmail\");\r", + " pm.expect(jsonData.email, '\"email\" must be ' + email).to.eql(email);\r", + "});\r", + "pm.test(\"Test user 'name' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('name');\r", + " var name = pm.collectionVariables.get(\"userName\");\r", + " pm.expect(jsonData.name, '\"name\" must be ' + name).to.eql(name);\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{userName}}\",\n \"email\": \"{{userEmail}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [] + }, + { + "name": "User update name", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " us = await api.addUser(user);\r", + " pm.collectionVariables.set(\"userId\", us.id);\r", + " us = rnd.getUser()\r", + " pm.collectionVariables.set(\"userName\", us.name);\r", + " pm.collectionVariables.set(\"userEmail\", us.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.be.ok;\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "pm.test(\"Test user 'id' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('id');\r", + " var id = pm.collectionVariables.get(\"userId\");\r", + " pm.expect(jsonData.id, '\"id\" must be ' + id).to.eql(Number(id));\r", + "});\r", + "pm.test(\"Test user 'name' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('name');\r", + " var name = pm.collectionVariables.get(\"userName\");\r", + " pm.expect(jsonData.name, '\"name\" must be ' + name).to.eql(name);\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{userName}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [] + }, + { + "name": "User update email", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " us = await api.addUser(user);\r", + " pm.collectionVariables.set(\"userId\", us.id);\r", + " us = rnd.getUser()\r", + " pm.collectionVariables.set(\"userName\", us.name);\r", + " pm.collectionVariables.set(\"userEmail\", us.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.be.ok;\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "pm.test(\"Test user 'id' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('id');\r", + " var id = pm.collectionVariables.get(\"userId\");\r", + " pm.expect(jsonData.id, '\"id\" must be ' + id).to.eql(Number(id));\r", + "});\r", + "pm.test(\"Test user 'email' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('email');\r", + " var email = pm.collectionVariables.get(\"userEmail\");\r", + " pm.expect(jsonData.email, '\"email\" must be ' + email).to.eql(email);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{userEmail}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [] + }, + { + "name": "User update with existing email", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " us = await api.addUser(user);\r", + " user2 = rnd.getUser();\r", + " us2 = await api.addUser(user2)\r", + " pm.collectionVariables.set(\"userId\", us2.id);\r", + " usa = rnd.getUser()\r", + " pm.collectionVariables.set(\"userName\", usa.name);\r", + " pm.collectionVariables.set(\"userEmail\", user.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 409\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([409, 500]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{userName}}\",\n \"email\": \"{{userEmail}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [] + }, + { + "name": "Get user", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " us = await api.addUser(user);\r", + " pm.collectionVariables.set(\"userId\", us.id);\r", + " pm.collectionVariables.set(\"userName\", us.name);\r", + " pm.collectionVariables.set(\"userEmail\", us.email);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.be.ok;\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "pm.test(\"Test user 'id' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('id');\r", + " var id = pm.collectionVariables.get(\"userId\");\r", + " pm.expect(jsonData.id, '\"id\" must be ' + id).to.eql(Number(id));\r", + "});\r", + "pm.test(\"Test user 'email' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('email');\r", + " var email = pm.collectionVariables.get(\"userEmail\");\r", + " pm.expect(jsonData.email, '\"email\" must be ' + email).to.eql(email);\r", + "});\r", + "pm.test(\"Test user 'name' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('name');\r", + " var name = pm.collectionVariables.get(\"userName\");\r", + " pm.expect(jsonData.name, '\"name\" must be ' + name).to.eql(name);\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [] + }, + { + "name": "User delete", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([200,204]);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " us = await api.addUser(user);\r", + " pm.collectionVariables.set(\"userId\", us.id);\r", + " pm.collectionVariables.set(\"userName\", us.name);\r", + " pm.collectionVariables.set(\"userEmail\", us.email);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/users/{{userId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "items", + "item": [ + { + "name": "Item create", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " item = rnd.getItem();\r", + " pm.collectionVariables.set(\"item\", item);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200 or 201\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([200,201]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "var item = pm.collectionVariables.get(\"item\");\r", + "\r", + "pm.test(\"Response data equal to request\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('id');\r", + " pm.expect(jsonData).to.have.property('name');\r", + " pm.expect(jsonData).to.have.property('description');\r", + " pm.expect(jsonData).to.have.property('available');\r", + " pm.expect(jsonData.name, `\"name\" must be ${item.name}`).to.eql(item.name);\r", + " pm.expect(jsonData.description, `\"description\" must be ${item.description}`).to.eql(item.description);\r", + " pm.expect(jsonData.available.toString(), `\"available\" must be ${item.available}`).to.eql(item.available.toString());\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{itemName}}\",\n \"description\": \"{{itemDescription}}\",\n \"available\": {{itemAvailable}}\n}" + }, + "url": { + "raw": "localhost:8080/items", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "items" + ] + } + }, + "response": [] + }, + { + "name": "Item create without X-Sharer-User-Id", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " item = rnd.getItem();\r", + " pm.collectionVariables.set(\"item\", item);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400 or 500\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([500, 400]);\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{itemName}}\",\n \"description\": \"{{itemDescription}}\",\n \"available\": {{itemAvailable}}\n}" + }, + "url": { + "raw": "localhost:8080/items", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "items" + ] + } + }, + "response": [] + }, + { + "name": "Item create with non-existent user", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " item = rnd.getItem();\r", + " pm.collectionVariables.set(\"item\", item);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id + '1');\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 404\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([404]);\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{itemName}}\",\n \"description\": \"{{itemDescription}}\",\n \"available\": {{itemAvailable}}\n}" + }, + "url": { + "raw": "localhost:8080/items", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "items" + ] + } + }, + "response": [] + }, + { + "name": "Item create without available field", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " item = rnd.getItem();\r", + " pm.collectionVariables.set(\"item\", item);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{itemName}}\",\n \"description\": \"{{itemDescription}}\"\n}" + }, + "url": { + "raw": "localhost:8080/items", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "items" + ] + } + }, + "response": [] + }, + { + "name": "Item create with empty name field", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "3", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"\",\n \"description\": \"Аккумуляторная отвертка\",\n \"available\": true\n}" + }, + "url": { + "raw": "{{baseUrl}}/items", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items" + ] + } + }, + "response": [] + }, + { + "name": "Item create with empty description field", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "3", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Отвертка\",\n \"available\": true\n}" + }, + "url": { + "raw": "{{baseUrl}}/items", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items" + ] + } + }, + "response": [] + }, + { + "name": "Item update", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.be.ok;", + "});", + "pm.test(\"Response have body\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + "});", + "var item = pm.collectionVariables.get(\"item\");", + "", + "pm.test(\"Response data equal to request\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('id');", + " pm.expect(jsonData).to.have.property('name');", + " pm.expect(jsonData).to.have.property('description');", + " pm.expect(jsonData).to.have.property('available');", + " pm.expect(jsonData.name, `\"name\" must be ${item.name}`).to.eql(item.name);", + " pm.expect(jsonData.description, `\"description\" must be ${item.description}`).to.eql(item.description);", + " pm.expect(jsonData.available.toString(), `\"available\" must be ${item.available}`).to.eql(item.available.toString());", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id);\r", + " item = rnd.getItem();\r", + " var it = await api.addItem(item, user.id)\r", + " item = rnd.getItem();\r", + " pm.collectionVariables.set(\"item\", item); \r", + " pm.collectionVariables.set(\"itemId\", it.id);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{itemName}}\",\n \"description\": \"{{itemDescription}}\",\n \"available\": {{itemAvailable}}\n}" + }, + "url": { + "raw": "{{baseUrl}}/items/{{itemId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items", + "{{itemId}}" + ] + } + }, + "response": [] + }, + { + "name": "Item update without X-Sharer-User-Id", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 500\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([500, 400]);", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id);\r", + " item = rnd.getItem();\r", + " var it = await api.addItem(item, user.id)\r", + " item = rnd.getItem();\r", + " pm.collectionVariables.set(\"item\", item); \r", + " pm.collectionVariables.set(\"itemId\", it.id);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text", + "disabled": true + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{itemName}}\",\n \"description\": \"{{itemDescription}}\",\n \"available\": {{itemAvailable}}\n}" + }, + "url": { + "raw": "{{baseUrl}}/items/{{itemId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items", + "{{itemId}}" + ] + } + }, + "response": [] + }, + { + "name": "Item update with other user", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 404\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([404, 403]);", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id + 1);\r", + " item = rnd.getItem();\r", + " var it = await api.addItem(item, user.id)\r", + " item = rnd.getItem();\r", + " pm.collectionVariables.set(\"item\", item); \r", + " pm.collectionVariables.set(\"itemId\", it.id);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{itemName}}\",\n \"description\": \"{{itemDescription}}\",\n \"available\": {{itemAvailable}}\n}" + }, + "url": { + "raw": "{{baseUrl}}/items/{{itemId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items", + "{{itemId}}" + ] + } + }, + "response": [] + }, + { + "name": "Item update available field", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.be.ok;", + "});", + "pm.test(\"Response have body\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + "});", + "var item = pm.collectionVariables.get(\"item\");", + "", + "pm.test(\"Response data equal to request\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('id');", + " pm.expect(jsonData).to.have.property('available');", + " pm.expect(jsonData.available.toString(), `\"available\" must be ${item.available}`).to.eql(item.available.toString());", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id);\r", + " item = rnd.getItem();\r", + " var it = await api.addItem(item, user.id)\r", + " item = rnd.getItem();\r", + " pm.collectionVariables.set(\"item\", item); \r", + " pm.collectionVariables.set(\"itemId\", it.id);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"available\": {{itemAvailable}}\n}" + }, + "url": { + "raw": "{{baseUrl}}/items/{{itemId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items", + "{{itemId}}" + ] + } + }, + "response": [] + }, + { + "name": "Item update description field", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.be.ok;", + "});", + "pm.test(\"Response have body\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + "});", + "var item = pm.collectionVariables.get(\"item\");", + "", + "pm.test(\"Response data equal to request\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('id');", + " pm.expect(jsonData).to.have.property('name');", + " pm.expect(jsonData).to.have.property('description');", + " pm.expect(jsonData).to.have.property('available');", + " pm.expect(jsonData.description, `\"description\" must be ${item.description}`).to.eql(item.description);", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id);\r", + " item = rnd.getItem();\r", + " var it = await api.addItem(item, user.id)\r", + " item = rnd.getItem();\r", + " pm.collectionVariables.set(\"item\", item); \r", + " pm.collectionVariables.set(\"itemId\", it.id);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"description\": \"{{itemDescription}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/items/{{itemId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items", + "{{itemId}}" + ] + } + }, + "response": [] + }, + { + "name": "Item update name field", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.be.ok;", + "});", + "pm.test(\"Response have body\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + "});", + "var item = pm.collectionVariables.get(\"item\");", + "", + "pm.test(\"Response data equal to request\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('id');", + " pm.expect(jsonData).to.have.property('name');", + " pm.expect(jsonData).to.have.property('description');", + " pm.expect(jsonData).to.have.property('available');", + " pm.expect(jsonData.name, `\"name\" must be ${item.name}`).to.eql(item.name);", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id);\r", + " item = rnd.getItem();\r", + " var it = await api.addItem(item, user.id)\r", + " item = rnd.getItem();\r", + " pm.collectionVariables.set(\"item\", item); \r", + " pm.collectionVariables.set(\"itemId\", it.id);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{itemName}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/items/{{itemId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items", + "{{itemId}}" + ] + } + }, + "response": [] + }, + { + "name": "Item get", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.be.ok;", + "});", + "pm.test(\"Response have body\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + "});", + "var item = pm.collectionVariables.get(\"item\");", + "", + "pm.test(\"Response data equal to request\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('id');", + " pm.expect(jsonData).to.have.property('name');", + " pm.expect(jsonData).to.have.property('description');", + " pm.expect(jsonData).to.have.property('available');", + " pm.expect(jsonData.name, `\"name\" must be ${item.name}`).to.eql(item.name);", + " pm.expect(jsonData.description, `\"description\" must be ${item.description}`).to.eql(item.description);", + " pm.expect(jsonData.available.toString(), `\"available\" must be ${item.available}`).to.eql(item.available.toString());", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id);\r", + " item = await api.addItem(rnd.getItem(), user.id)\r", + " pm.collectionVariables.set(\"item\", item);\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "3", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/items/{{itemId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items", + "{{itemId}}" + ] + } + }, + "response": [] + }, + { + "name": "Get all items from user", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.be.ok;", + "});", + "pm.test(\"Test list item response\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.length, 'List length must be 2').to.eql(2);", + "});", + "", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id);\r", + " await api.addItem(rnd.getItem(), user.id)\r", + " await api.addItem(rnd.getItem(), user.id)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/items", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items" + ] + } + }, + "response": [] + }, + { + "name": "Item search", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.be.ok;", + "});", + "pm.test(\"Test list item response\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.length, 'List length must be 1').to.eql(1);", + " pm.expect(jsonData[0].name.toUpperCase(), 'Name should include ' + pm.collectionVariables.get(\"searchString\")).to.eql(pm.collectionVariables.get(\"searchString\"))", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id);\r", + " it = rnd.getItem()\r", + " it.available = true\r", + " item = await api.addItem(it, user.id)\r", + " pm.collectionVariables.set(\"item\", item);\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"searchString\", item.name.toUpperCase());\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "1", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/items/search?text={{searchString}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items", + "search" + ], + "query": [ + { + "key": "text", + "value": "{{searchString}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Item search unavailable", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.be.ok;", + "});", + "pm.test(\"Test list item response\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.length, 'List length must be 0').to.eql(0);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id);\r", + " it = rnd.getItem()\r", + " it.available = false\r", + " item = await api.addItem(it, user.id)\r", + " pm.collectionVariables.set(\"item\", item);\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"searchString\", item.name.toUpperCase());\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "1", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/items/search?text={{searchString}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items", + "search" + ], + "query": [ + { + "key": "text", + "value": "{{searchString}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Item search empty", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.be.ok;", + "});", + "pm.test(\"Test search item response\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.length, 'List length must be 0').to.eql(0);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "1", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/items/search?text=", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items", + "search" + ], + "query": [ + { + "key": "text", + "value": "" + } + ] + } + }, + "response": [] + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "API = class {\r", + " constructor(postman, verbose = false, baseUrl = \"http://localhost:8080\") {\r", + " this.baseUrl = baseUrl;\r", + " this.pm = postman;\r", + " this._verbose = verbose;\r", + " }\r", + "\r", + " async addUser(user, id=0, verbose=null) {\r", + " return this.post(\"/users\", user, id, \"Ошибка при добавлении нового пользователя: \", verbose);\r", + " }\r", + "\r", + " async addItem(item, id=0, verbose=null) {\r", + " return this.post(\"/items\", item, id, \"Ошибка при добавлении новой вещи: \", verbose);\r", + " }\r", + "\r", + " async addRequest(request, id=0, verbose=null) {\r", + " return this.post(\"/requests\", request, id, \"Ошибка при добавлении нового запроса: \", verbose);\r", + " }\r", + " \r", + " async post(path, body, id=0, errorText = \"Ошибка при выполнении post-запроса: \", verbose=null) {\r", + " return this.sendRequest(\"POST\", path, body, id, errorText, verbose);\r", + " }\r", + "\r", + " async patch(path, body = null, id=0, errorText = \"Ошибка при выполнении patch-запроса: \", verbose=null) {\r", + " return this.sendRequest(\"PATCH\", path, body, id, errorText, verbose);\r", + " }\r", + "\r", + " async get(path, body = null, id=0, errorText = \"Ошибка при выполнении get-запроса: \", verbose=null) {\r", + " return this.sendRequest(\"GET\", path, body, id, errorText, verbose);\r", + " }\r", + "\r", + " async put(path, body = null, id=0, errorText = \"Ошибка при выполнении put-запроса: \", verbose=null) {\r", + " return this.sendRequest(\"PUT\", path, body, id, errorText, verbose);\r", + " }\r", + "\r", + " async delete(path, body = null, id=0, errorText = \"Ошибка при выполнении delte-запроса: \", verbose=null) {\r", + " return this.sendRequest(\"DELETE\", path, body, id, errorText, verbose);\r", + " }\r", + "\r", + " async sendRequest(method, path, body=null, id=0, errorText = \"Ошибка при выполнении запроса: \", verbose=null) {\r", + " return new Promise((resolve, reject) => {\r", + " verbose = verbose == null ? this._verbose : verbose;\r", + " var req = {};\r", + " if (id == 0){\r", + " req = {\r", + " url: this.baseUrl + path,\r", + " method: method,\r", + " body: body == null ? \"\" : JSON.stringify(body),\r", + " header: { \"Content-Type\": \"application/json\"},\r", + " };\r", + " }else{\r", + " req = {\r", + " url: this.baseUrl + path,\r", + " method: method,\r", + " body: body == null ? \"\" : JSON.stringify(body),\r", + " header: [{\r", + " \"key\": \"X-Sharer-User-Id\",\r", + " \"value\": id,\r", + " \"type\": \"text\",\r", + " },\r", + " {\r", + " \"key\": \"Content-Type\",\r", + " \"name\": \"Content-Type\",\r", + " \"value\": \"application/json\",\r", + " \"type\": \"text\"\r", + " }]\r", + " };\r", + " }\r", + " if(verbose) {\r", + " console.log(\"Отправляю запрос: \", req);\r", + " }\r", + "\r", + " try {\r", + " this.pm.sendRequest(req, (error, response) => {\r", + " if(error || (response.code >= 400 && response.code <= 599)) {\r", + " let err = error ? error : JSON.stringify(response.json());\r", + " console.error(\"При выполнении запроса к серверу возникла ошибка.\\n\", err,\r", + " \"\\nДля отладки проблемы повторите такой же запрос к вашей программе \" + \r", + " \"на локальном компьютере. Данные запроса:\\n\", JSON.stringify(request));\r", + "\r", + " reject(new Error(errorText + err));\r", + " }\r", + " if(verbose) {\r", + " console.log(\"Результат обработки запроса: код состояния - \", response.code, \", тело: \", response.json());\r", + " }\r", + " if (response.stream.length === 0){\r", + " resolve(null);\r", + " }else{\r", + " resolve(response.json());\r", + " }\r", + " });\r", + " \r", + " } catch(err) {\r", + " if(verbose) {\r", + " console.error(errorText, err);\r", + " }\r", + " return Promise.reject(err);\r", + " }\r", + " });\r", + " }\r", + "};\r", + "\r", + "RandomUtils = class {\r", + " constructor() {}\r", + "\r", + " getUser() {\r", + " return {\r", + " name: pm.variables.replaceIn('{{$randomFullName}}'),\r", + " email: pm.variables.replaceIn('{{$randomEmail}}'),\r", + " };\r", + " }\r", + "\r", + " getRequest() {\r", + " return {\r", + " description: this.getWord(50)\r", + " };\r", + " }\r", + "\r", + " getItem() {\r", + " return {\r", + " name: this.getWord(10),\r", + " description: this.getWord(50),\r", + " available: pm.variables.replaceIn('{{$randomBoolean}}')\t\r", + " };\r", + " }\r", + "\r", + " getItemForRequest(id) {\r", + " return {\r", + " name: this.getWord(10),\r", + " description: this.getWord(50),\r", + " available: pm.variables.replaceIn('{{$randomBoolean}}'),\r", + " requestId: id\r", + " };\r", + " }\r", + "\r", + " getFilm(director=null) {\r", + " let date = new Date(new Date(1960, 0, 1).getTime() + Math.random() * (new Date(2010, 0, 1).getTime() - new Date(1960, 0, 1).getTime()));\r", + " var toReturn = {\r", + " name: this.getWord(15),\r", + " description: this.getWord(50),\r", + " releaseDate: date.toISOString().slice(0,10),\r", + " duration: Math.floor(Math.random() * (180 - 60 + 1) + 60),\r", + " mpa: { id: Math.floor(Math.random() * (5 - 1 + 1) + 1)},\r", + " genres: [{ id: Math.floor(Math.random() * (6 - 1 + 1) + 1)}]\r", + " };\r", + " if (director!==null)\r", + " toReturn.directors = [{ id: director.id}];\r", + " return toReturn;\r", + " }\r", + "\r", + "\r", + " getWord(length = 1) {\r", + " let result = '';\r", + " const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\r", + " const charactersLength = characters.length;\r", + " let counter = 0;\r", + " while (counter < length) {\r", + " result += characters.charAt(Math.floor(Math.random() * charactersLength));\r", + " counter += 1;\r", + " }\r", + " return result;\r", + " }\r", + "\r", + " getName(length = 1) {\r", + " let result = '';\r", + " const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\r", + " const charactersLength = characters.length;\r", + " let counter = 0;\r", + " while (counter < length) {\r", + " result += characters.charAt(Math.floor(Math.random() * charactersLength));\r", + " counter += 1;\r", + " }\r", + " return result;\r", + " }\r", + "\r", + "}" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "baseUrl", + "value": "http://localhost:8080" + }, + { + "key": "userName", + "value": "" + }, + { + "key": "userEmail", + "value": "" + }, + { + "key": "userId", + "value": "1", + "type": "string" + }, + { + "key": "item", + "value": "" + }, + { + "key": "itemName", + "value": "" + }, + { + "key": "itemAvailable", + "value": "" + }, + { + "key": "itemDescription", + "value": "" + }, + { + "key": "itemId", + "value": "" + }, + { + "key": "searchString", + "value": "" + } + ] +} From 10a18c9478f953c80bbeac66b5e0e67630ee2fbb Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Mon, 26 Aug 2024 09:38:18 +0300 Subject: [PATCH 3/4] #1 Fix Set userId into X-Sharer-User-Id --- postman/sprint.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/postman/sprint.json b/postman/sprint.json index a3f02fac..d04cf55f 100644 --- a/postman/sprint.json +++ b/postman/sprint.json @@ -1312,7 +1312,7 @@ "header": [ { "key": "X-Sharer-User-Id", - "value": "3", + "value": "{{userId}}", "type": "text" }, { @@ -1364,7 +1364,7 @@ "header": [ { "key": "X-Sharer-User-Id", - "value": "3", + "value": "{{userId}}", "type": "text" }, { @@ -2112,7 +2112,7 @@ "header": [ { "key": "X-Sharer-User-Id", - "value": "3", + "value": "{{userId}}", "type": "text" }, { @@ -2296,7 +2296,7 @@ "header": [ { "key": "X-Sharer-User-Id", - "value": "1", + "value": "{{userId}}", "type": "text" }, { @@ -2397,7 +2397,7 @@ "header": [ { "key": "X-Sharer-User-Id", - "value": "1", + "value": "{{userId}}", "type": "text" }, { @@ -2451,7 +2451,7 @@ "header": [ { "key": "X-Sharer-User-Id", - "value": "1", + "value": "{{userId}}", "type": "text" }, { From 706207da8f42bf497197640a6871362d3391f07d Mon Sep 17 00:00:00 2001 From: Evstafev Date: Wed, 25 Jun 2025 21:23:34 +0700 Subject: [PATCH 4/4] =?UTF-8?q?=D0=A2=D0=97=2014?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ru/practicum/shareit/ShareItApp.java | 6 +- .../ru/practicum/shareit/booking/Booking.java | 7 - .../shareit/booking/BookingController.java | 53 ++++++- .../shareit/booking/BookingMapper.java | 26 ++++ .../ru/practicum/shareit/booking/Status.java | 8 ++ .../booking/ValidateBookingController.java | 27 ++++ .../shareit/booking/dto/BookingDto.java | 21 +++ .../shareit/booking/model/Booking.java | 29 ++++ .../booking/repository/BookingRepository.java | 22 +++ .../repository/BookingRepositoryInMemory.java | 73 ++++++++++ .../booking/service/BookingService.java | 22 +++ .../booking/service/BookingServiceImpl.java | 132 ++++++++++++++++++ .../exception/DuplicateDataException.java | 7 + .../shareit/exception/ErrorHandler.java | 19 +++ .../shareit/exception/ErrorIsNull.java | 8 ++ .../shareit/exception/NotDataException.java | 7 + .../exception/ValidationException.java | 7 + .../shareit/item/ItemController.java | 62 +++++++- .../ru/practicum/shareit/item/ItemMapper.java | 30 ++++ .../shareit/item/ValidateItemController.java | 36 +++++ .../practicum/shareit/item/dto/ItemDto.java | 12 ++ .../ru/practicum/shareit/item/model/Item.java | 13 ++ .../item/repository/ItemRepository.java | 20 +++ .../repository/ItemRepositoryInMemory.java | 84 +++++++++++ .../shareit/item/service/ItemService.java | 21 +++ .../shareit/item/service/ItemServiceImpl.java | 84 +++++++++++ .../shareit/request/ItemRequest.java | 7 - .../request/ItemRequestController.java | 58 +++++++- .../shareit/request/RequestMapper.java | 27 ++++ .../shareit/request/dto/ItemRequestDto.java | 10 ++ .../shareit/request/model/ItemRequest.java | 20 +++ .../repository/ItemRequestRepository.java | 24 ++++ .../ItemRequestRepositoryInMemory.java | 82 +++++++++++ .../request/service/ItemRequestService.java | 21 +++ .../service/ItemRequestServiceImpl.java | 101 ++++++++++++++ .../java/ru/practicum/shareit/user/User.java | 7 - .../shareit/user/UserController.java | 57 +++++++- .../ru/practicum/shareit/user/UserMapper.java | 34 +++++ .../shareit/user/ValidateUserController.java | 40 ++++++ .../practicum/shareit/user/dto/UserDto.java | 12 ++ .../ru/practicum/shareit/user/model/User.java | 19 +++ .../user/repository/UserRepository.java | 23 +++ .../repository/UserRepositoryInMemory.java | 80 +++++++++++ .../shareit/user/service/UserService.java | 19 +++ .../shareit/user/service/UserServiceImpl.java | 90 ++++++++++++ .../ru/practicum/shareit/ShareItTests.java | 6 +- 46 files changed, 1538 insertions(+), 35 deletions(-) delete mode 100644 src/main/java/ru/practicum/shareit/booking/Booking.java create mode 100644 src/main/java/ru/practicum/shareit/booking/BookingMapper.java create mode 100644 src/main/java/ru/practicum/shareit/booking/Status.java create mode 100644 src/main/java/ru/practicum/shareit/booking/ValidateBookingController.java create mode 100644 src/main/java/ru/practicum/shareit/booking/model/Booking.java create mode 100644 src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java create mode 100644 src/main/java/ru/practicum/shareit/booking/repository/BookingRepositoryInMemory.java create mode 100644 src/main/java/ru/practicum/shareit/booking/service/BookingService.java create mode 100644 src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java create mode 100644 src/main/java/ru/practicum/shareit/exception/DuplicateDataException.java create mode 100644 src/main/java/ru/practicum/shareit/exception/ErrorHandler.java create mode 100644 src/main/java/ru/practicum/shareit/exception/ErrorIsNull.java create mode 100644 src/main/java/ru/practicum/shareit/exception/NotDataException.java create mode 100644 src/main/java/ru/practicum/shareit/exception/ValidationException.java create mode 100644 src/main/java/ru/practicum/shareit/item/ItemMapper.java create mode 100644 src/main/java/ru/practicum/shareit/item/ValidateItemController.java create mode 100644 src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java create mode 100644 src/main/java/ru/practicum/shareit/item/repository/ItemRepositoryInMemory.java create mode 100644 src/main/java/ru/practicum/shareit/item/service/ItemService.java create mode 100644 src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java delete mode 100644 src/main/java/ru/practicum/shareit/request/ItemRequest.java create mode 100644 src/main/java/ru/practicum/shareit/request/RequestMapper.java create mode 100644 src/main/java/ru/practicum/shareit/request/model/ItemRequest.java create mode 100644 src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java create mode 100644 src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepositoryInMemory.java create mode 100644 src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java create mode 100644 src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java delete mode 100644 src/main/java/ru/practicum/shareit/user/User.java create mode 100644 src/main/java/ru/practicum/shareit/user/UserMapper.java create mode 100644 src/main/java/ru/practicum/shareit/user/ValidateUserController.java create mode 100644 src/main/java/ru/practicum/shareit/user/dto/UserDto.java create mode 100644 src/main/java/ru/practicum/shareit/user/model/User.java create mode 100644 src/main/java/ru/practicum/shareit/user/repository/UserRepository.java create mode 100644 src/main/java/ru/practicum/shareit/user/repository/UserRepositoryInMemory.java create mode 100644 src/main/java/ru/practicum/shareit/user/service/UserService.java create mode 100644 src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java diff --git a/src/main/java/ru/practicum/shareit/ShareItApp.java b/src/main/java/ru/practicum/shareit/ShareItApp.java index a00ad567..a10a87da 100644 --- a/src/main/java/ru/practicum/shareit/ShareItApp.java +++ b/src/main/java/ru/practicum/shareit/ShareItApp.java @@ -6,8 +6,8 @@ @SpringBootApplication public class ShareItApp { - public static void main(String[] args) { - SpringApplication.run(ShareItApp.class, args); - } + public static void main(String[] args) { + SpringApplication.run(ShareItApp.class, args); + } } diff --git a/src/main/java/ru/practicum/shareit/booking/Booking.java b/src/main/java/ru/practicum/shareit/booking/Booking.java deleted file mode 100644 index 2d9c6668..00000000 --- a/src/main/java/ru/practicum/shareit/booking/Booking.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.booking; - -/** - * TODO Sprint add-bookings. - */ -public class Booking { -} diff --git a/src/main/java/ru/practicum/shareit/booking/BookingController.java b/src/main/java/ru/practicum/shareit/booking/BookingController.java index b94493d4..5d1c34f2 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -1,12 +1,61 @@ package ru.practicum.shareit.booking; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.service.BookingService; + +import java.util.Collection; /** * TODO Sprint add-bookings. */ + +@Slf4j +@RequiredArgsConstructor @RestController @RequestMapping(path = "/bookings") public class BookingController { + private final BookingService bookingService; + + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public Booking createBooking(@RequestHeader("X-Sharer-User-Id") long userId, @RequestBody BookingDto bookingDto) { + log.info("создание запроса бронирования"); + return bookingService.saveBooking(userId, bookingDto); + } + + @PatchMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public Booking updateBooking(@RequestHeader("X-Sharer-User-Id") long userId, @PathVariable("id") long id, + @RequestBody BookingDto bookingDto) { + log.info("обновление запроса бронирования"); + return bookingService.updateBooking(userId, id, bookingDto); + } + + @DeleteMapping("/{itemId}") + @ResponseStatus(HttpStatus.OK) + public void delBooking(@RequestHeader("X-Sharer-User-Id") long userId, @PathVariable("itemId") long itemId) { + log.info("удаление запроса бронирования"); + bookingService.delBooking(userId, itemId); + } + + @GetMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + BookingDto geyBookingById(@PathVariable("id") long id) { + log.info("поиск запроса бронирования id" + id); + return bookingService.geyBookingById(id); + } + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public Collection findAllBooking() { + log.info("вывод списка запросов бронирования"); + return bookingService.findAllBooking(); + } + } diff --git a/src/main/java/ru/practicum/shareit/booking/BookingMapper.java b/src/main/java/ru/practicum/shareit/booking/BookingMapper.java new file mode 100644 index 00000000..f12cc421 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/BookingMapper.java @@ -0,0 +1,26 @@ +package ru.practicum.shareit.booking; + +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.model.Booking; + +public class BookingMapper { + public static Booking mapToBooking(BookingDto bookingDto) { + Booking booking = new Booking(); + booking.setId(bookingDto.getId() != null ? bookingDto.getId() : null); + booking.setStart(bookingDto.getStart()); + booking.setEnd(bookingDto.getEnd()); + booking.setItem(bookingDto.getItem()); + booking.setStatus(bookingDto.getStatus()); + return booking; + } + + public static BookingDto mapToBookingDto(Booking booking) { + BookingDto bookingDto = new BookingDto(); + bookingDto.setId(booking.getId() != null ? booking.getId() : null); + bookingDto.setStart(booking.getStart()); + bookingDto.setEnd(booking.getEnd()); + bookingDto.setItem(booking.getItem()); + bookingDto.setStatus(booking.getStatus()); + return bookingDto; + } +} diff --git a/src/main/java/ru/practicum/shareit/booking/Status.java b/src/main/java/ru/practicum/shareit/booking/Status.java new file mode 100644 index 00000000..51e88948 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/Status.java @@ -0,0 +1,8 @@ +package ru.practicum.shareit.booking; + +public enum Status { + WAITTING, + APPROVED, + REJECTED, + CANCELED +} diff --git a/src/main/java/ru/practicum/shareit/booking/ValidateBookingController.java b/src/main/java/ru/practicum/shareit/booking/ValidateBookingController.java new file mode 100644 index 00000000..711ba47a --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/ValidateBookingController.java @@ -0,0 +1,27 @@ +package ru.practicum.shareit.booking; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.exception.NotDataException; + +@Slf4j +@Component +public class ValidateBookingController { + + public void validateBookingDto(BookingDto bookingDto) { + if (bookingDto.getItem() == null) { + log.warn("item не может быть пустым"); + throw new NotDataException("item не может быть пустым"); + } + + if (bookingDto.getStart() == null) { + log.warn("время начало бронирования не может быть пустым"); + throw new NotDataException("время начало бронирования не может быть пустым"); + } + if (bookingDto.getEnd() == null) { + log.warn("время начало бронирования не может быть пустым"); + throw new NotDataException("время начало бронирования не может быть пустым"); + } + } +} diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java index 861de9e0..d9cd64e9 100644 --- a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java +++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java @@ -1,7 +1,28 @@ package ru.practicum.shareit.booking.dto; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import ru.practicum.shareit.booking.Status; + +import java.time.LocalDateTime; + /** * TODO Sprint add-bookings. */ +@Data public class BookingDto { + + private Long id; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm") + private LocalDateTime start; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm") + private LocalDateTime end; + + private Long item; + + // private long booker; //оставил на всякий случай + + private Status status; } diff --git a/src/main/java/ru/practicum/shareit/booking/model/Booking.java b/src/main/java/ru/practicum/shareit/booking/model/Booking.java new file mode 100644 index 00000000..b40fc62c --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/model/Booking.java @@ -0,0 +1,29 @@ +package ru.practicum.shareit.booking.model; + + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import ru.practicum.shareit.booking.Status; + +import java.time.LocalDateTime; + +/** + * TODO Sprint add-bookings. + */ +@Data +public class Booking { + + private Long id; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm") + private LocalDateTime start; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm") + private LocalDateTime end; + + private Long item; + + private long booker; + + private Status status; +} diff --git a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java new file mode 100644 index 00000000..ce490326 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java @@ -0,0 +1,22 @@ +package ru.practicum.shareit.booking.repository; + +import ru.practicum.shareit.booking.model.Booking; + +import java.util.Collection; + +public interface BookingRepository { + + Booking save(Booking booking); + + Booking update(long Id, Booking booking); + + + void del(long itemId); + + + Booking getById(long id); + + Collection findAll(); + + +} diff --git a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepositoryInMemory.java b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepositoryInMemory.java new file mode 100644 index 00000000..24c6340e --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepositoryInMemory.java @@ -0,0 +1,73 @@ +package ru.practicum.shareit.booking.repository; + + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.booking.model.Booking; + + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@Repository +public class BookingRepositoryInMemory implements BookingRepository { + + private final Map bookingMap = new HashMap<>(); + + @Override + public Booking save(Booking booking) { + if (booking.getId() != null) { + log.warn("нельзя создать запрос с id" + booking.getId()); + } + booking.setId(getNextId()); + bookingMap.put(booking.getId(), booking); + log.info("создание запроса бронирования id" + booking.getId()); + return booking; + } + + @Override + public Booking update(long id, Booking booking) { + Booking oldBooking = bookingMap.get(id); + if (booking.getStart() != null) { + oldBooking.setStart(booking.getStart()); + } + if (booking.getEnd() != null) { + oldBooking.setEnd(booking.getEnd()); + } + if (booking.getStatus() != null) { + oldBooking.setStatus(booking.getStatus()); + } + log.info("обновление запроса бронирования id" + oldBooking.getId()); + + return oldBooking; + } + + @Override + public void del(long itemId) { + log.info("удаление запроса бронирования id" + itemId); + bookingMap.remove(itemId); + } + + @Override + public Booking getById(long id) { + log.info("поиск запроса бронирования id" + id); + return bookingMap.get(id); + } + + @Override + public Collection findAll() { + log.info("запрос всех бронирований"); + return bookingMap.values(); + } + + private long getNextId() { + long currentMaxId = bookingMap.keySet() + .stream() + .mapToLong(id -> id) + .max() + .orElse(0); + return ++currentMaxId; + } +} diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingService.java b/src/main/java/ru/practicum/shareit/booking/service/BookingService.java new file mode 100644 index 00000000..0d0897f1 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/service/BookingService.java @@ -0,0 +1,22 @@ +package ru.practicum.shareit.booking.service; + +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.model.Booking; + +import java.util.Collection; + +public interface BookingService { + + Booking saveBooking(long userId, BookingDto bookingDto); + + Booking updateBooking(long userId, long itemId, BookingDto bookingDto); + + + void delBooking(long userId, long itemId); + + + BookingDto geyBookingById(long id); + + + Collection findAllBooking(); +} diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java b/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java new file mode 100644 index 00000000..948dba85 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java @@ -0,0 +1,132 @@ +package ru.practicum.shareit.booking.service; + + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.booking.BookingMapper; +import ru.practicum.shareit.booking.Status; +import ru.practicum.shareit.booking.ValidateBookingController; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.repository.BookingRepository; +import ru.practicum.shareit.exception.NotDataException; +import ru.practicum.shareit.item.service.ItemService; +import ru.practicum.shareit.user.service.UserService; + +import java.util.Collection; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor + +public class BookingServiceImpl implements BookingService { + private final BookingRepository bookingRepository; + private final UserService userService; + private final ItemService itemService; + private final ValidateBookingController validateBookingController; + + @Override + public Booking saveBooking(long userId, BookingDto bookingDto) { + validateBookingController.validateBookingDto(bookingDto); + if (userService.findByIdUser(userId) == null) { + log.warn("нет такого пользователя"); + throw new NotDataException("нет такого пользователя"); + } + if (itemService.getItemByItemId(bookingDto.getItem()) == null) { + log.warn("нет такой вещи"); + throw new NotDataException("нет такой вещи"); + } + if (itemService.getItemByItemId(bookingDto.getItem()).getOwner() == userId) { + log.warn("нельзя бронировать свою вещь"); + throw new NotDataException("нельзя бронировать свою вещь"); + } + if (!itemService.getItemByItemId(bookingDto.getItem()).getAvailable()) { + log.warn("нельзя бронировать"); + throw new NotDataException("нельзя бронировать"); + } + Booking booking = BookingMapper.mapToBooking(bookingDto); + booking.setBooker(userId); + booking.setStatus(Status.WAITTING); + booking = bookingRepository.save(booking); + log.info("сохранение запроса бронирование id" + booking.getId()); + return booking; + } + + @Override + public Booking updateBooking(long userId, long itemId, BookingDto bookingDto) { + + Booking booking = bookingRepository.getById(itemId); + + if (userService.findByIdUser(userId) == null) { + log.warn("нет такого пользователя"); + throw new NotDataException("нет такого пользователя"); + } + if (booking == null) { + log.warn("нет такого запроса"); + throw new NotDataException("нет такого запроса"); + } + + + System.out.println(userId); + System.out.println(booking); + System.out.println(bookingDto); + + + if (booking.getBooker() != userId && itemService.getItemByItemId(booking.getItem()).getOwner() != userId) { + log.warn("вы не собственник запроса и не владелиц вещи"); + throw new NotDataException("вы не собственник запроса и не владелиц вещи"); + } + if (booking.getBooker() == userId && itemService.getItemByItemId(booking.getItem()).getOwner() != userId) { + if (booking.getStatus() != bookingDto.getStatus() && bookingDto.getStatus() != Status.CANCELED) { + log.warn("нельзя поставить такой статус бронирования"); + throw new NotDataException("нельзя поставить такой статус бронирования"); + } + } + if (booking.getBooker() != userId && itemService.getItemByItemId(booking.getItem()).getOwner() == userId) { + if (booking.getStatus() != bookingDto.getStatus() && bookingDto.getStatus() == Status.CANCELED) { + log.warn("нельзя поставить такой статус бронирования"); + throw new NotDataException("нельзя поставить такой статус бронирования"); + } + } + log.info("обновили запрос"); + + return bookingRepository.update(itemId, BookingMapper.mapToBooking(bookingDto)); + } + + @Override + public void delBooking(long userId, long itemId) { + + Booking booking = bookingRepository.getById(itemId); + if (booking == null) { + log.warn("нет такого запроса"); + throw new NotDataException("нет такого запроса"); + } + if (booking.getBooker() != userId) { + log.warn("не собственник запроса"); + throw new NotDataException("не собственник запроса"); + } + bookingRepository.del(itemId); + + } + + @Override + public BookingDto geyBookingById(long id) { + Booking booking = bookingRepository.getById(id); + if (booking == null) { + log.warn("пустой запрос"); + throw new NotDataException("пустой запрос"); + } + log.info("получаем запрос бронирования по id" + id); + return BookingMapper.mapToBookingDto(booking); + } + + @Override + public Collection findAllBooking() { + log.info("запрос всех запросов бронирования"); + return bookingRepository.findAll().stream() + .map(booking -> BookingMapper.mapToBookingDto(booking)) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/DuplicateDataException.java b/src/main/java/ru/practicum/shareit/exception/DuplicateDataException.java new file mode 100644 index 00000000..c453ae0b --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/DuplicateDataException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class DuplicateDataException extends RuntimeException { + public DuplicateDataException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java b/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java new file mode 100644 index 00000000..4445cbe4 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.exception; + +import org.springframework.http.HttpStatus; + +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler + @ResponseStatus(HttpStatus.NOT_FOUND) + public NotDataException handlerIsNull(final ErrorIsNull e) { + + return new NotDataException(e.getMessage()); + + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorIsNull.java b/src/main/java/ru/practicum/shareit/exception/ErrorIsNull.java new file mode 100644 index 00000000..809ef8f2 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ErrorIsNull.java @@ -0,0 +1,8 @@ +package ru.practicum.shareit.exception; + +public class ErrorIsNull extends NullPointerException { + + public ErrorIsNull(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/NotDataException.java b/src/main/java/ru/practicum/shareit/exception/NotDataException.java new file mode 100644 index 00000000..2830fbbd --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/NotDataException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class NotDataException extends RuntimeException { + public NotDataException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/ValidationException.java b/src/main/java/ru/practicum/shareit/exception/ValidationException.java new file mode 100644 index 00000000..59043da1 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ValidationException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class ValidationException extends RuntimeException { + public ValidationException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java index bb17668b..7724a40a 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -1,12 +1,70 @@ package ru.practicum.shareit.item; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.service.ItemService; + +import java.util.Collection; /** * TODO Sprint add-controllers. */ + +@Slf4j @RestController +@RequiredArgsConstructor @RequestMapping("/items") public class ItemController { + private final ItemService itemService; + + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public Item createItem(@RequestHeader("X-Sharer-User-Id") long userId, @RequestBody ItemDto itemDto) { + log.info("запрос создания вещи"); + return itemService.saveItem(userId, itemDto); + } + + @PatchMapping("/{itemId}") + @ResponseStatus(HttpStatus.OK) + public Item updateItem(@RequestHeader("X-Sharer-User-Id") long userId, @PathVariable("itemId") long itemId, + @RequestBody ItemDto itemDto) { + log.info("запрос обновление вещи"); + return itemService.updateItem(userId, itemId, itemDto); + } + + @DeleteMapping("/{itemId}") + @ResponseStatus(HttpStatus.OK) + public void delItem(@RequestHeader("X-Sharer-User-Id") long userId, @PathVariable("itemId") long itemId) { + log.info("запрос удаления вещи"); + itemService.deleteItem(userId, itemId); + } + + @GetMapping("/{itemId}") + @ResponseStatus(HttpStatus.OK) + public Item getItemById(@PathVariable("itemId") long itemId) { + log.info("запрос вещи по id " + itemId); + return itemService.getItemByItemId(itemId); + } + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public Collection getItemByUserID(@RequestHeader("X-Sharer-User-Id") long userId) { + log.info("запрос вещей у пользователя id " + userId); + + return itemService.getAllItemByUserId(userId); + } + + @GetMapping("/search") + @ResponseStatus(HttpStatus.OK) + public Collection searchItemByName(@RequestParam String text) { + log.info("запрос поиска вещи name " + text); + return itemService.findItemByName(text); + + } + } diff --git a/src/main/java/ru/practicum/shareit/item/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/ItemMapper.java new file mode 100644 index 00000000..b174a169 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/ItemMapper.java @@ -0,0 +1,30 @@ +package ru.practicum.shareit.item; + +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; + +public class ItemMapper { + + public static Item mapToItem(ItemDto itemDto) { + Item item = new Item(); + item.setId(itemDto.getId() != null ? item.getId() : null); + item.setName(itemDto.getName()); + item.setDescription(itemDto.getDescription()); + item.setAvailable(itemDto.getAvailable()); + item.setOwner(item.getOwner()); + item.setRequest(item.getRequest()); + return item; + } + + public static ItemDto mapToItemDto(Item item) { + ItemDto itemDto = new ItemDto(); + itemDto.setId(item.getId() != null ? item.getId() : null); + itemDto.setName(item.getName()); + itemDto.setDescription(item.getDescription()); + itemDto.setAvailable(item.getAvailable()); + // itemDto.setOwner(item.getOwner()); //оставил на всякий случай + itemDto.setRequest(item.getRequest()); + return itemDto; + } + +} diff --git a/src/main/java/ru/practicum/shareit/item/ValidateItemController.java b/src/main/java/ru/practicum/shareit/item/ValidateItemController.java new file mode 100644 index 00000000..12459fc3 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/ValidateItemController.java @@ -0,0 +1,36 @@ +package ru.practicum.shareit.item; + + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.practicum.shareit.exception.NotDataException; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.repository.ItemRepository; + +@Slf4j +@Component +public class ValidateItemController { + private final ItemRepository itemRepository; + + public ValidateItemController(ItemRepository itemRepository) { + this.itemRepository = itemRepository; + } + + public void validateItemDto(ItemDto itemDto) { + if (itemDto.getName() == null || itemDto.getName().isEmpty()) { + log.warn("name не может быть пустым"); + throw new NotDataException("name не может быть пустым"); + } + + if (itemDto.getDescription() == null || itemDto.getDescription().isEmpty()) { + log.warn("Description не может быть пустым"); + throw new NotDataException("Description не может быть пустым"); + } + ; + if (itemDto.getAvailable() == null) { + log.warn("Available не может быть пустым"); + throw new NotDataException("Available не может быть пустым"); + } + + } +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java index 9319d7d7..89703bd0 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -1,7 +1,19 @@ package ru.practicum.shareit.item.dto; +import lombok.Data; + /** * TODO Sprint add-controllers. */ +@Data public class ItemDto { + private Long id; + private String name; + private String description; + + private Boolean available; + + // private long owner; //оставил на всякий случай + + private long request; } diff --git a/src/main/java/ru/practicum/shareit/item/model/Item.java b/src/main/java/ru/practicum/shareit/item/model/Item.java index 44eb73dd..5ca4b27c 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -1,7 +1,20 @@ package ru.practicum.shareit.item.model; +import lombok.Data; + /** * TODO Sprint add-controllers. */ +@Data public class Item { + + private Long id; + private String name; + private String description; + + private Boolean available; + + private long owner; + + private long request; } diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java new file mode 100644 index 00000000..42903275 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.item.repository; + +import ru.practicum.shareit.item.model.Item; + +import java.util.Collection; + +public interface ItemRepository { + Item save(Item item); + + void delete(long id); + + Item update(Item item); + + Item getByItemId(long id); + + Collection getAllByUserId(long id); + + Collection findItemByName(String name); + +} diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepositoryInMemory.java b/src/main/java/ru/practicum/shareit/item/repository/ItemRepositoryInMemory.java new file mode 100644 index 00000000..8ab991e7 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepositoryInMemory.java @@ -0,0 +1,84 @@ +package ru.practicum.shareit.item.repository; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.item.model.Item; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Repository +public class ItemRepositoryInMemory implements ItemRepository { + + private final Map itemMap = new HashMap<>(); + + @Override + public Item save(Item item) { + if (item.getId() != null) { + log.warn("нельзя создать пользователя с id " + item.getId()); + throw new ValidationException("нельзя создать вещь с id " + item.getId()); + } + item.setId(getNextId()); + itemMap.put(item.getId(), item); + log.info("создана вещь c id " + item.getId()); + return item; + } + + @Override + public void delete(long id) { + log.info("вещь удалена id " + id); + itemMap.remove(id); + } + + @Override + public Item update(Item item) { + Item oldItem = itemMap.get(item.getId()); + if (item.getName() != null) { + oldItem.setName(item.getName()); + } + if (item.getDescription() != null) { + oldItem.setDescription(item.getDescription()); + } + if (item.getAvailable() != null) { + oldItem.setAvailable(item.getAvailable()); + } + log.info("вещь обновлена id " + oldItem.getId()); + return oldItem; + } + + @Override + public Item getByItemId(long id) { + log.info("запрос вещи id " + id); + return itemMap.get(id); + } + + @Override + public Collection getAllByUserId(long id) { + log.info("запрос списка вещей пользователя id " + id); + return itemMap.values().stream() + .filter(item -> item.getOwner() == id) + .collect(Collectors.toList()); + } + + @Override + public Collection findItemByName(String name) { + log.info("запрос списка вещей по имени " + name); + return itemMap.values().stream() + .filter(item -> name.equals(item.getName().toLowerCase()) || name.equals(item.getDescription().toLowerCase())) + .filter(item -> item.getAvailable()) + .collect(Collectors.toList()); + } + + private long getNextId() { + long currentMaxId = itemMap.keySet() + .stream() + .mapToLong(id -> id) + .max() + .orElse(0); + return ++currentMaxId; + } +} diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemService.java b/src/main/java/ru/practicum/shareit/item/service/ItemService.java new file mode 100644 index 00000000..bd806875 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -0,0 +1,21 @@ +package ru.practicum.shareit.item.service; + +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; + +import java.util.Collection; + +public interface ItemService { + + Item saveItem(long userId, ItemDto itemDto); + + void deleteItem(long userId, long id); + + Item updateItem(long userId, long itemId, ItemDto itemDto); + + Item getItemByItemId(long id); + + Collection getAllItemByUserId(long id); + + Collection findItemByName(String name); +} diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java new file mode 100644 index 00000000..75bb1d7c --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -0,0 +1,84 @@ +package ru.practicum.shareit.item.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.ErrorIsNull; +import ru.practicum.shareit.exception.NotDataException; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.item.ItemMapper; +import ru.practicum.shareit.item.ValidateItemController; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.util.Collection; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ItemServiceImpl implements ItemService { + private final ItemRepository itemRepository; + private final UserRepository userRepository; + + private final ValidateItemController validateItemController; + + @Override + public Item saveItem(long userId, ItemDto itemDto) { + if (userRepository.findById(userId) == null) { + throw new ErrorIsNull("нет такого пользователя"); + } + validateItemController.validateItemDto(itemDto); + Item item = ItemMapper.mapToItem(itemDto); + item.setOwner(userId); + item = itemRepository.save(item); + log.info("пердана вещь для создания"); + return item; + } + + @Override + public void deleteItem(long userId, long id) { + if (getItemByItemId(id) == null) { + throw new NotDataException("нет вещи с таким id " + id); + } + if (getItemByItemId(id).getOwner() != userId) { + throw new ValidationException("вы не являетесь владельцем вещи"); + } + itemRepository.delete(id); + log.info("удаление вещи, передан id " + id); + } + + @Override + public Item updateItem(long userId, long itemId, ItemDto itemDto) { + if (getItemByItemId(itemId) == null) { + throw new NotDataException("нет вещи с таким id " + itemId); + } + if (getItemByItemId(itemId).getOwner() != userId) { + throw new ErrorIsNull("вы не являетесь владельцем вещи"); + } + Item item = ItemMapper.mapToItem(itemDto); + item.setId(itemId); + log.info("обновление вещи, передан id " + itemId); + return itemRepository.update(item); + } + + @Override + public Item getItemByItemId(long id) { + log.info("передан запрос вещи по id " + id); + return itemRepository.getByItemId(id); + } + + @Override + public Collection getAllItemByUserId(long id) { + log.info("передан запрос списка вещей пользователя id " + id); + return itemRepository.getAllByUserId(id); + } + + @Override + public Collection findItemByName(String name) { + + log.info("передан запрос поиска вещей по названию " + name.toLowerCase()); + return itemRepository.findItemByName(name.toLowerCase()); + } +} diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/ItemRequest.java deleted file mode 100644 index 95d6f23c..00000000 --- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.request; - -/** - * TODO Sprint add-item-requests. - */ -public class ItemRequest { -} diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/src/main/java/ru/practicum/shareit/request/ItemRequestController.java index 064e2e9c..0f2d9f88 100644 --- a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java +++ b/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -1,12 +1,66 @@ package ru.practicum.shareit.request; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.service.ItemRequestService; + +import java.util.Collection; /** * TODO Sprint add-item-requests. */ +@Slf4j @RestController +@RequiredArgsConstructor @RequestMapping(path = "/requests") public class ItemRequestController { + + private final ItemRequestService itemRequestService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + ItemRequest createItemRequest(@RequestHeader("X-Sharer-User-Id") long userId, @RequestBody ItemRequestDto itemRequestDto) { + log.info("создание запроса создания вещи"); + return itemRequestService.saveItemRequest(userId, itemRequestDto); + } + + @PatchMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + ItemRequest updateItemRequest(@RequestHeader("X-Sharer-User-Id") long userId, @PathVariable("id") long id, + @RequestBody ItemRequestDto itemRequestDto) { + log.info("обновление запроса создания вещи"); + return itemRequestService.updateItemRequest(userId, id, itemRequestDto); + } + + @GetMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + ItemRequestDto findIdItemRequestById(@PathVariable("id") long id) { + log.info("вывод запроса создания вещи id " + id); + return itemRequestService.findIdItemRequestById(id); + } + + @GetMapping("/search") + @ResponseStatus(HttpStatus.OK) + ItemRequestDto findItemRequestByDescription(@RequestParam String item) { + log.info("вывод запроса по названию вещи"); + return itemRequestService.findItemRequestByDescription(item); + } + + @GetMapping + @ResponseStatus(HttpStatus.OK) + Collection findAll() { + log.info("вывод всех запросов создания вещи"); + return itemRequestService.findAll(); + } + + @DeleteMapping("/{itemId}") + @ResponseStatus(HttpStatus.OK) + void delete(@RequestHeader("X-Sharer-User-Id") long userId, @PathVariable("itemId") long id) { + log.info("удаление запроса создания вещи"); + itemRequestService.delete(userId, id); + } } diff --git a/src/main/java/ru/practicum/shareit/request/RequestMapper.java b/src/main/java/ru/practicum/shareit/request/RequestMapper.java new file mode 100644 index 00000000..d2a0aa49 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/RequestMapper.java @@ -0,0 +1,27 @@ +package ru.practicum.shareit.request; + +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.model.ItemRequest; + +public class RequestMapper { + public static ItemRequest mapToRequest(ItemRequestDto itemRequestDto) { + ItemRequest itemRequest = new ItemRequest(); + // itemRequest.setId(itemRequestDto.getId() != null ? itemRequestDto.getId() : null); //оставил на всякий случай + itemRequest.setDescription(itemRequestDto.getDescription()); + itemRequest.setRequestor(itemRequestDto.getRequestor()); + // itemRequest.setCreated(itemRequestDto.getCreated()); //оставил на всякий случай + return itemRequest; + } + + public static ItemRequestDto mapToRequestDto(ItemRequest itemRequest) { + ItemRequestDto itemRequestDto = new ItemRequestDto(); + + // itemRequestDto.setId(itemRequest.getId()); //оставил на всякий случай + itemRequestDto.setDescription(itemRequest.getDescription()); + itemRequestDto.setRequestor(itemRequest.getRequestor()); + // itemRequestDto.setCreated(itemRequest.getCreated()); //оставил на всякий случай + + return itemRequestDto; + } + +} diff --git a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java index 7b3ed544..ff326715 100644 --- a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java +++ b/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java @@ -1,7 +1,17 @@ package ru.practicum.shareit.request.dto; +import lombok.Data; + + /** * TODO Sprint add-item-requests. */ +@Data public class ItemRequestDto { + // private Long id; + private String description; + private Long requestor; + + // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm") + // private LocalDateTime created; //оставил на всякий случай } diff --git a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java new file mode 100644 index 00000000..37d590c3 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.request.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * TODO Sprint add-item-requests. + */ +@Data +public class ItemRequest { + private Long id; + private String description; + private Long requestor; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm") + private LocalDateTime created; + +} diff --git a/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java b/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java new file mode 100644 index 00000000..290b6b44 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java @@ -0,0 +1,24 @@ +package ru.practicum.shareit.request.repository; + +import ru.practicum.shareit.request.model.ItemRequest; + +import java.util.Collection; +import java.util.Optional; + +public interface ItemRequestRepository { + + + ItemRequest save(ItemRequest itemRequest); + + ItemRequest update(ItemRequest itemRequest); + + ItemRequest findById(long id); + + Optional findByDescription(String description); + + Collection findAll(); + + void delete(long id); + + +} diff --git a/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepositoryInMemory.java b/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepositoryInMemory.java new file mode 100644 index 00000000..e2a6a190 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepositoryInMemory.java @@ -0,0 +1,82 @@ +package ru.practicum.shareit.request.repository; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.request.model.ItemRequest; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + + +@Slf4j +@Repository +public class ItemRequestRepositoryInMemory implements ItemRequestRepository { + + private final Map itemRequestMap = new HashMap<>(); + + @Override + public ItemRequest save(ItemRequest itemRequest) { + if (itemRequest.getId() != null) { + log.warn("нельзя создать запрос с id " + itemRequest.getId()); + throw new ValidationException("нельзя создать запрос с id " + itemRequest.getId()); + } + itemRequest.setId(getNextId()); + itemRequestMap.put(itemRequest.getId(), itemRequest); + log.info("создан запрос c id " + itemRequest.getId()); + return itemRequest; + } + + + @Override + public ItemRequest update(ItemRequest itemRequest) { + ItemRequest itemRequestOld = itemRequestMap.get(itemRequest.getId()); + if (itemRequest.getDescription() != null) { + itemRequestOld.setDescription(itemRequest.getDescription()); + } + if (itemRequest.getRequestor() != null) { + itemRequestOld.setRequestor(itemRequest.getRequestor()); + } + log.info("обновлен запрос c id " + itemRequest.getId()); + return itemRequestOld; + } + + @Override + public ItemRequest findById(long id) { + log.info("запись запроса возвращена id " + id); + return itemRequestMap.get(id); + } + + @Override + public Optional findByDescription(String description) { + log.info("запрос по имени " + description); + + return itemRequestMap.values().stream() + .filter(itemRequest -> itemRequest.getDescription().toLowerCase().equals(description)) + .findFirst(); + + } + + @Override + public Collection findAll() { + log.info("весь список запросов"); + return itemRequestMap.values(); + } + + @Override + public void delete(long id) { + log.info("удаляем запрос id " + id); + itemRequestMap.remove(id); + } + + private long getNextId() { + long currentMaxId = itemRequestMap.keySet() + .stream() + .mapToLong(id -> id) + .max() + .orElse(0); + return ++currentMaxId; + } +} diff --git a/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java b/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java new file mode 100644 index 00000000..4eb09fbe --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java @@ -0,0 +1,21 @@ +package ru.practicum.shareit.request.service; + +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.model.ItemRequest; + +import java.util.Collection; + +public interface ItemRequestService { + + ItemRequest saveItemRequest (long userId,ItemRequestDto itemRequestDto); + + ItemRequest updateItemRequest (long userId, long itemId,ItemRequestDto itemRequestDto); + + ItemRequestDto findIdItemRequestById(long id); + + ItemRequestDto findItemRequestByDescription (String description); + + Collection findAll (); + + void delete (long userId, long id); +} diff --git a/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java b/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java new file mode 100644 index 00000000..b2d2e36e --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java @@ -0,0 +1,101 @@ +package ru.practicum.shareit.request.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.NotDataException; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.request.RequestMapper; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Collectors; + + +@Slf4j +@Service +@RequiredArgsConstructor +public class ItemRequestServiceImpl implements ItemRequestService { + + + private final ItemRequestRepository itemRequestRepository; + private final UserRepository userRepository; + + @Override + public ItemRequest saveItemRequest(long userId, ItemRequestDto itemRequestDto) { + if (userRepository.findById(userId)==null){ + throw new NotDataException("нет такого пользователя"); + } + if (itemRequestDto.getDescription()==null){ + throw new NotDataException("description не может быть пустым"); + } + if (itemRequestDto.getDescription().isEmpty() || itemRequestDto.getDescription().isBlank() ){ + throw new NotDataException("description не может быть пустым"); + } + ItemRequest itemRequest = RequestMapper.mapToRequest(itemRequestDto); + itemRequest.setRequestor(userId); + itemRequest.setCreated(LocalDateTime.now()); + itemRequest=itemRequestRepository.save(itemRequest); + log.info("создан запрос id " + itemRequest.getId()); + return itemRequest; + } + + @Override + public ItemRequest updateItemRequest(long userId, long itemId ,ItemRequestDto itemRequestDto) { + if (itemRequestRepository.findById(itemId)==null){ + throw new NotDataException("нет запроса вещи с таким id "+ itemId); + } + if (findIdItemRequestById(itemId).getRequestor()!=userId){ + throw new NotDataException("вы не являетесь владельцем запроса вещи"); + } + ItemRequest itemRequest = RequestMapper.mapToRequest(itemRequestDto); + itemRequest.setId(itemId); + itemRequest.setCreated(LocalDateTime.now()); + log.info("обновление вещи, передан id "+itemId); + itemRequest = itemRequestRepository.update(itemRequest); + return itemRequest; + } + + @Override + public ItemRequestDto findIdItemRequestById(long id) { + log.info("получение запроса по id"); + return RequestMapper.mapToRequestDto(itemRequestRepository.findById(id)); + } + + @Override + public ItemRequestDto findItemRequestByDescription(String description) { + log.info("получение запроса по имени вещи"); + Optional optionalItemRequestDto = itemRequestRepository.findByDescription(description.toLowerCase()); + if ( optionalItemRequestDto.isEmpty() ){ + throw new NotDataException("нет такого запроса вещи"); + } + return RequestMapper.mapToRequestDto(optionalItemRequestDto.get()); + } + + @Override + public Collection findAll() { + log.info("получение списка запросов вещей"); + return itemRequestRepository.findAll().stream() + .map(RequestMapper::mapToRequestDto) + .collect(Collectors.toList()); + } + + @Override + public void delete(long userId, long id) { + if (itemRequestRepository.findById(id)==null){ + throw new NotDataException("нет вещи с таким id "+ id); + } + if (itemRequestRepository.findById(id).getRequestor() !=userId){ + throw new ValidationException("вы не являетесь владельцем вещи"); + } + itemRequestRepository.delete(id); + log.info("удаление запроса вещи, передан id "+id); + + + } +} diff --git a/src/main/java/ru/practicum/shareit/user/User.java b/src/main/java/ru/practicum/shareit/user/User.java deleted file mode 100644 index ae6e7f33..00000000 --- a/src/main/java/ru/practicum/shareit/user/User.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.user; - -/** - * TODO Sprint add-controllers. - */ -public class User { -} diff --git a/src/main/java/ru/practicum/shareit/user/UserController.java b/src/main/java/ru/practicum/shareit/user/UserController.java index 03039b9d..9066d815 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/src/main/java/ru/practicum/shareit/user/UserController.java @@ -1,12 +1,65 @@ package ru.practicum.shareit.user; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.service.UserService; + +import java.util.Collection; /** * TODO Sprint add-controllers. */ +@Slf4j @RestController @RequestMapping(path = "/users") public class UserController { + + private final UserService userService; + + @Autowired + public UserController(UserService userService) { + this.userService = userService; + } + + @GetMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public User getUserId(@Positive(message = "неверное значение") @PathVariable long id) { + log.info("запрос пользателя по id " + id); + return userService.findByIdUser(id); + } + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public Collection getAllUsers() { + log.info("запрос списка всех пользователей"); + return userService.findAllUser(); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public User createUser(@Valid @RequestBody UserDto userDto) { + log.info("запрос добавления нового пользователя"); + return userService.saveUser(userDto); + } + + @PatchMapping("/{userId}") + @ResponseStatus(HttpStatus.OK) + public User updateUser(@Valid @PathVariable("userId") long userId, @RequestBody UserDto userDto) { + log.info("запрос обновления пользователя с id " + userId); + return userService.updateUser(userId, userDto); + } + + @DeleteMapping("/{userId}") + @ResponseStatus(HttpStatus.OK) + public void delUser(@PathVariable("userId") long userId) { + log.info("удаление пользователя с id " + userId); + userService.deleteUser(userId); + } } + diff --git a/src/main/java/ru/practicum/shareit/user/UserMapper.java b/src/main/java/ru/practicum/shareit/user/UserMapper.java new file mode 100644 index 00000000..509a8208 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/UserMapper.java @@ -0,0 +1,34 @@ +package ru.practicum.shareit.user; + +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + +public class UserMapper { + + public static User mapToUser(UserDto userDto) { + User user = new User(); + + user.setName(userDto.getName()); + user.setEmail(userDto.getEmail()); + // user.setId(userDto.getId() != null ? userDto.getId() : null); // //оставил на всякий случай + return user; + } + + + public static UserDto mapToUserDto(User user) { + UserDto userDto = new UserDto(); + userDto.setName(user.getName()); + userDto.setEmail(user.getEmail()); +//userDto.setId(user.getId() != null ? user.getId() : null); // //оставил на всякий случай + return userDto; + } + + public static User mapToUserUpdate(long userId, UserDto userDto) { + User user = new User(); + + user.setName(userDto.getName()); + user.setEmail(userDto.getEmail()); + user.setId(userId); + return user; + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/ValidateUserController.java b/src/main/java/ru/practicum/shareit/user/ValidateUserController.java new file mode 100644 index 00000000..37634351 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/ValidateUserController.java @@ -0,0 +1,40 @@ +package ru.practicum.shareit.user; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.practicum.shareit.exception.DuplicateDataException; +import ru.practicum.shareit.exception.NotDataException; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.repository.UserRepository; + +@Slf4j +@Component +public class ValidateUserController { + + private final UserRepository userRepository; + + public ValidateUserController(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public void validateUserDto(UserDto userDto) { + if (userDto.getName() == null || userDto.getName().isEmpty()) { + log.warn("name не может быть пустым\""); + throw new NotDataException("name не может быть пустым"); + } + + if (userRepository.findByEmail(userDto.getEmail()).isPresent()) { + log.warn("такой email уже есть"); + throw new DuplicateDataException("такой email уже есть"); + } + if (userDto.getEmail() == null || userDto.getEmail().isEmpty()) { + log.warn("email не может быть пустым"); + throw new NotDataException("email не может быть пустым"); + } + + if (!userDto.getEmail().contains("@")) { + log.warn("введен не email"); + throw new NotDataException("введен не email"); + } + } +} diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java new file mode 100644 index 00000000..7fb09ca3 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit.user.dto; + +import lombok.Data; + +@Data +public class UserDto { + // private Long id; //оставил на всякий случай + + private String name; + + private String email; +} diff --git a/src/main/java/ru/practicum/shareit/user/model/User.java b/src/main/java/ru/practicum/shareit/user/model/User.java new file mode 100644 index 00000000..83d0ded5 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/model/User.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.user.model; + +import jakarta.validation.constraints.Email; +import lombok.Data; +import lombok.RequiredArgsConstructor; + +/** + * TODO Sprint add-controllers. + */ +@Data +@RequiredArgsConstructor +public class User { + private Long id; + + private String name; + + @Email + private String email; +} diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java new file mode 100644 index 00000000..3555d44c --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java @@ -0,0 +1,23 @@ +package ru.practicum.shareit.user.repository; + +import ru.practicum.shareit.user.model.User; + +import java.util.Collection; +import java.util.Optional; + + +public interface UserRepository { + + User save(User user); + + User update(User user); + + User findById(long id); + + Collection findAll(); + + void delete(long id); + + Optional findByEmail(String email); + +} diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepositoryInMemory.java b/src/main/java/ru/practicum/shareit/user/repository/UserRepositoryInMemory.java new file mode 100644 index 00000000..3f5856e2 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/repository/UserRepositoryInMemory.java @@ -0,0 +1,80 @@ +package ru.practicum.shareit.user.repository; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.user.model.User; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + + +@Slf4j +@Repository + +public class UserRepositoryInMemory implements UserRepository { + + private final Map userMap = new HashMap<>(); + + @Override + public User save(User user) { + if (user.getId() != null) { + log.warn("нельзя создать пользователя с id " + user.getId()); + throw new ValidationException("нельзя создать пользователя с id " + user.getId()); + } + user.setId(getNextId()); + userMap.put(user.getId(), user); + log.info("создан user c id " + user.getId()); + return user; + } + + @Override + public User update(User user) { + User oldUser = userMap.get(user.getId()); + if (user.getName() != null) { + oldUser.setName(user.getName()); + } + if (user.getEmail() != null) { + oldUser.setEmail(user.getEmail()); + } + log.info("запись пользователя обновлена id " + oldUser.getId()); + return oldUser; + } + + @Override + public User findById(long id) { + log.info("запись пользователя возвращена id " + id); + return userMap.get(id); + } + + @Override + public Collection findAll() { + log.info("возвращен списов всех пользователе"); + return userMap.values(); + } + + @Override + public void delete(long id) { + log.info("пользвоателе удален id " + id); + userMap.remove(id); + } + + @Override + public Optional findByEmail(String email) { + log.info("поиск пользователя по email " + email); + Optional findUser = userMap.values().stream() + .filter(user -> user.getEmail().equals(email)).findFirst(); + return findUser; + } + + private long getNextId() { + long currentMaxId = userMap.keySet() + .stream() + .mapToLong(id -> id) + .max() + .orElse(0); + return ++currentMaxId; + } +} diff --git a/src/main/java/ru/practicum/shareit/user/service/UserService.java b/src/main/java/ru/practicum/shareit/user/service/UserService.java new file mode 100644 index 00000000..d2a51ce6 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserService.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.user.service; + +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + +import java.util.Collection; + +public interface UserService { + + User saveUser(UserDto userDto); + + User updateUser(Long userId, UserDto userDto); + + User findByIdUser(long id); + + Collection findAllUser(); + + void deleteUser(long id); +} diff --git a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java new file mode 100644 index 00000000..17bf2c2f --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -0,0 +1,90 @@ +package ru.practicum.shareit.user.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; +import ru.practicum.shareit.exception.DuplicateDataException; +import ru.practicum.shareit.exception.NotDataException; +import ru.practicum.shareit.user.UserMapper; +import ru.practicum.shareit.user.repository.UserRepository; +import ru.practicum.shareit.user.ValidateUserController; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Collectors; + +@Validated +@Slf4j +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + private final ValidateUserController validateUserController; + + @Override + public User saveUser(UserDto userDto) { + validateUserController.validateUserDto(userDto); + User user = UserMapper.mapToUser(userDto); + log.info("пердан пользователь для создания"); + user = userRepository.save(user); + return user; + } + + @Override + public User updateUser(Long userId, UserDto userDto) { + User updateUser = userRepository.findById(userId); + if (updateUser == null) { + throw new NotDataException("нет такого пользователя"); + } + if (userDto.getEmail() == null) { + userDto.setEmail(updateUser.getEmail()); + } + if ((userDto.getName() == null)) { + userDto.setName(updateUser.getName()); + } + Optional findUser = userRepository.findByEmail(userDto.getEmail()); + if (findUser.isEmpty() || findUser.get().getId() == userId) { + + updateUser = UserMapper.mapToUserUpdate(userId, userDto); + updateUser = userRepository.update(updateUser); + log.info("передали пользователя для обновления"); + return updateUser; + + } else { + log.warn("такой email уже есть"); + throw new DuplicateDataException("такой email уже есть"); + } + } + + @Override + public User findByIdUser(long id) { + User user = userRepository.findById(id); + if (user == null) { + throw new NotDataException("нет такого пользоваетля"); + } + log.info("передали запрос пользователя по id " + id); + return user; + } + + @Override + public Collection findAllUser() { + log.info("передали запрос вывести всех пользователей"); + return userRepository.findAll() + .stream() + .map(UserMapper::mapToUserDto) + .collect(Collectors.toList()); + } + + @Override + public void deleteUser(long id) { + User user = userRepository.findById(id); + if (user == null) { + throw new NotDataException("нет такого пользоваетля"); + } + log.info("передали удаление пользователя по id " + id); + userRepository.delete(id); + } +} diff --git a/src/test/java/ru/practicum/shareit/ShareItTests.java b/src/test/java/ru/practicum/shareit/ShareItTests.java index 4d79052f..8502acd1 100644 --- a/src/test/java/ru/practicum/shareit/ShareItTests.java +++ b/src/test/java/ru/practicum/shareit/ShareItTests.java @@ -6,8 +6,8 @@ @SpringBootTest class ShareItTests { - @Test - void contextLoads() { - } + @Test + void contextLoads() { + } }