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..2f047fab --- /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 diff --git a/postman/sprint.json b/postman/sprint.json new file mode 100644 index 00000000..d04cf55f --- /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": "{{userId}}", + "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": "{{userId}}", + "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": "{{userId}}", + "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": "{{userId}}", + "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": "{{userId}}", + "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": "{{userId}}", + "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": "" + } + ] +} diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java index bb17668b..8fa493a4 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -1,12 +1,56 @@ 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.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.service.ItemService; -/** - * TODO Sprint add-controllers. - */ +import java.util.List; + +@Slf4j @RestController @RequestMapping("/items") +@RequiredArgsConstructor public class ItemController { -} + private final ItemService itemService; + + @GetMapping + public List findAllByUser(@RequestHeader("X-Sharer-User-Id") Long userId) { + log.info("GET /items для пользователя {}", userId); + return itemService.findAllByUser(userId); + } + + @GetMapping("/{itemId}") + public ItemDto findById(@PathVariable Long itemId) { + log.info("GET /items/{}", itemId); + return itemService.findById(itemId); + } + + @PostMapping + public ItemDto create(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody ItemDto itemDto) { + log.info("POST /items для пользователя {}", userId); + return itemService.create(userId, itemDto); + } + + @PatchMapping("/{itemId}") + public ItemDto update(@RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long itemId, + @RequestBody ItemDto itemDto) { + log.info("PATCH /items/{} для пользователя {}", itemId, userId); + return itemService.update(userId, itemId, itemDto); + } + + @DeleteMapping("/{itemId}") + public void delete(@PathVariable Long itemId) { + log.info("DELETE /items/{}", itemId); + itemService.delete(itemId); + } + + @GetMapping("/search") + public List search(@RequestParam String text) { + log.info("GET /items/search?text={}", text); + return itemService.search(text); + } +} \ No newline at end of file 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..bc570a74 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/ItemMapper.java @@ -0,0 +1,32 @@ +package ru.practicum.shareit.item; + +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; + +public class ItemMapper { + public static ItemDto toItemDto(Item item) { + if (item == null) { + return null; + } + ItemDto dto = new ItemDto(); + dto.setId(item.getId()); + dto.setName(item.getName()); + dto.setDescription(item.getDescription()); + dto.setAvailable(item.getAvailable()); + dto.setRequestId(item.getRequestId()); + return dto; + } + + public static Item toItem(ItemDto itemDto) { + if (itemDto == null) { + return null; + } + Item item = new Item(); + item.setId(itemDto.getId()); + item.setName(itemDto.getName()); + item.setDescription(itemDto.getDescription()); + item.setAvailable(itemDto.getAvailable()); + item.setRequestId(itemDto.getRequestId()); + return item; + } +} \ No newline at end of file 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..9fdd86e2 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,12 @@ package ru.practicum.shareit.item.dto; -/** - * TODO Sprint add-controllers. - */ +import lombok.Data; + +@Data public class ItemDto { -} + private Long id; + private String name; + private String description; + private Boolean available; + private Long requestId; +} \ No newline at end of file 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..11c6d5c3 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,13 @@ package ru.practicum.shareit.item.model; -/** - * TODO Sprint add-controllers. - */ +import lombok.Data; + +@Data public class Item { -} + private Long id; + private String name; + private String description; + private Boolean available; + private Long ownerId; + private Long requestId; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java new file mode 100644 index 00000000..c5800e37 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java @@ -0,0 +1,56 @@ +package ru.practicum.shareit.item.repository; + +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.item.model.Item; +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +@Repository +public class InMemoryItemRepository implements ItemRepository { + private final Map items = new HashMap<>(); + private final AtomicLong idGenerator = new AtomicLong(1); + + @Override + public List findAllByOwnerId(Long ownerId) { + return items.values().stream() + .filter(item -> ownerId.equals(item.getOwnerId())) + .collect(Collectors.toList()); + } + + @Override + public Optional findById(Long id) { + return Optional.ofNullable(items.get(id)); + } + + @Override + public Item save(Item item) { + item.setId(idGenerator.getAndIncrement()); + items.put(item.getId(), item); + return item; + } + + @Override + public Item update(Item item) { + items.put(item.getId(), item); + return item; + } + + @Override + public void delete(Long id) { + items.remove(id); + } + + @Override + public List search(String text) { + if (text == null || text.isBlank()) { + return Collections.emptyList(); + } + String lowerText = text.toLowerCase(); + return items.values().stream() + .filter(Item::getAvailable) + .filter(item -> (item.getName() != null && item.getName().toLowerCase().contains(lowerText)) + || (item.getDescription() != null && item.getDescription().toLowerCase().contains(lowerText))) + .collect(Collectors.toList()); + } +} \ No newline at end of file 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..0d7efc47 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.item.repository; + +import ru.practicum.shareit.item.model.Item; +import java.util.List; +import java.util.Optional; + +public interface ItemRepository { + List findAllByOwnerId(Long ownerId); + + Optional findById(Long id); + + Item save(Item item); + + Item update(Item item); + + void delete(Long id); + + List search(String text); +} \ No newline at end of file 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..f57bd230 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.item.service; + +import ru.practicum.shareit.item.dto.ItemDto; +import java.util.List; + +public interface ItemService { + List findAllByUser(Long userId); + + ItemDto findById(Long id); + + ItemDto create(Long userId, ItemDto itemDto); + + ItemDto update(Long userId, Long itemId, ItemDto itemDto); + + void delete(Long id); + + List search(String text); +} \ No newline at end of file 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..99a81143 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -0,0 +1,88 @@ +package ru.practicum.shareit.item.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.item.ItemMapper; +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.List; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ItemServiceImpl implements ItemService { + private final ItemRepository itemRepository; + private final UserRepository userRepository; + + @Override + public List findAllByUser(Long userId) { + log.info("Получение всех вещей пользователя {}", userId); + return itemRepository.findAllByOwnerId(userId).stream() + .map(ItemMapper::toItemDto) + .collect(Collectors.toList()); + } + + @Override + public ItemDto findById(Long id) { + log.info("Получение вещи с id {}", id); + Item item = itemRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Вещь не найдена")); + return ItemMapper.toItemDto(item); + } + + @Override + public ItemDto create(Long userId, ItemDto itemDto) { + log.info("Создание вещи для пользователя {}", userId); + userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("Пользователь не найден")); + + Item item = ItemMapper.toItem(itemDto); + item.setOwnerId(userId); + + Item saved = itemRepository.save(item); + return ItemMapper.toItemDto(saved); + } + + @Override + public ItemDto update(Long userId, Long itemId, ItemDto itemDto) { + log.info("Обновление вещи {} для пользователя {}", itemId, userId); + Item existing = itemRepository.findById(itemId) + .orElseThrow(() -> new RuntimeException("Вещь не найдена")); + + if (!userId.equals(existing.getOwnerId())) { + throw new RuntimeException("Только владелец может редактировать вещь"); + } + + if (itemDto.getName() != null) { + existing.setName(itemDto.getName()); + } + if (itemDto.getDescription() != null) { + existing.setDescription(itemDto.getDescription()); + } + if (itemDto.getAvailable() != null) { + existing.setAvailable(itemDto.getAvailable()); + } + + Item updated = itemRepository.update(existing); + return ItemMapper.toItemDto(updated); + } + + @Override + public void delete(Long id) { + log.info("Удаление вещи {}", id); + itemRepository.delete(id); + } + + @Override + public List search(String text) { + log.info("Поиск вещей по тексту: {}", text); + return itemRepository.search(text).stream() + .map(ItemMapper::toItemDto) + .collect(Collectors.toList()); + } +} \ No newline at end of file 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..83266723 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/src/main/java/ru/practicum/shareit/user/UserController.java @@ -1,12 +1,47 @@ package ru.practicum.shareit.user; -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.web.bind.annotation.*; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.service.UserService; -/** - * TODO Sprint add-controllers. - */ +import java.util.List; + +@Slf4j @RestController -@RequestMapping(path = "/users") +@RequestMapping("/users") +@RequiredArgsConstructor public class UserController { -} + private final UserService userService; + + @GetMapping + public List findAll() { + log.info("GET /users"); + return userService.findAll(); + } + + @GetMapping("/{id}") + public UserDto findById(@PathVariable Long id) { + log.info("GET /users/{}", id); + return userService.findById(id); + } + + @PostMapping + public UserDto create(@RequestBody UserDto userDto) { + log.info("POST /users - создание пользователя"); + return userService.create(userDto); + } + + @PatchMapping("/{id}") + public UserDto update(@PathVariable Long id, @RequestBody UserDto userDto) { + log.info("PATCH /users/{} - обновление пользователя", id); + return userService.update(id, userDto); + } + + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + log.info("DELETE /users/{}", id); + userService.delete(id); + } +} \ No newline at end of file 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..f8292e4f --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/UserMapper.java @@ -0,0 +1,28 @@ +package ru.practicum.shareit.user; + +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + +public class UserMapper { + public static UserDto toUserDto(User user) { + if (user == null) { + return null; + } + UserDto dto = new UserDto(); + dto.setId(user.getId()); + dto.setName(user.getName()); + dto.setEmail(user.getEmail()); + return dto; + } + + public static User toUser(UserDto userDto) { + if (userDto == null) { + return null; + } + User user = new User(); + user.setId(userDto.getId()); + user.setName(userDto.getName()); + user.setEmail(userDto.getEmail()); + return user; + } +} \ No newline at end of file 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..c3101d78 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,10 @@ +package ru.practicum.shareit.user.dto; + +import lombok.Data; + +@Data +public class UserDto { + private Long id; + private String name; + private String email; +} \ No newline at end of file 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..c30dbe09 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/model/User.java @@ -0,0 +1,10 @@ +package ru.practicum.shareit.user.model; + +import lombok.Data; + +@Data +public class User { + private Long id; + private String name; + private String email; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java b/src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java new file mode 100644 index 00000000..ade68078 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java @@ -0,0 +1,40 @@ +package ru.practicum.shareit.user.repository; + +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.user.model.User; +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; + +@Repository +public class InMemoryUserRepository implements UserRepository { + private final Map users = new HashMap<>(); + private final AtomicLong idGenerator = new AtomicLong(1); + + @Override + public List findAll() { + return new ArrayList<>(users.values()); + } + + @Override + public Optional findById(Long id) { + return Optional.ofNullable(users.get(id)); + } + + @Override + public User save(User user) { + user.setId(idGenerator.getAndIncrement()); + users.put(user.getId(), user); + return user; + } + + @Override + public User update(User user) { + users.put(user.getId(), user); + return user; + } + + @Override + public void delete(Long id) { + users.remove(id); + } +} \ No newline at end of file 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..691b47df --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.user.repository; + +import ru.practicum.shareit.user.model.User; +import java.util.List; +import java.util.Optional; + +public interface UserRepository { + List findAll(); + + Optional findById(Long id); + + User save(User user); + + User update(User user); + + void delete(Long id); +} \ No newline at end of file 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..c94c54ee --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserService.java @@ -0,0 +1,16 @@ +package ru.practicum.shareit.user.service; + +import ru.practicum.shareit.user.dto.UserDto; +import java.util.List; + +public interface UserService { + List findAll(); + + UserDto findById(Long id); + + UserDto create(UserDto userDto); + + UserDto update(Long id, UserDto userDto); + + void delete(Long id); +} \ No newline at end of file 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..c06ef488 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -0,0 +1,66 @@ +package ru.practicum.shareit.user.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.user.UserMapper; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + + @Override + public List findAll() { + log.info("Получение всех пользователей"); + return userRepository.findAll().stream() + .map(UserMapper::toUserDto) + .collect(Collectors.toList()); + } + + @Override + public UserDto findById(Long id) { + log.info("Получение пользователя с id {}", id); + User user = userRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Пользователь не найден")); + return UserMapper.toUserDto(user); + } + + @Override + public UserDto create(UserDto userDto) { + log.info("Создание пользователя с email {}", userDto.getEmail()); + User user = UserMapper.toUser(userDto); + User saved = userRepository.save(user); + return UserMapper.toUserDto(saved); + } + + @Override + public UserDto update(Long id, UserDto userDto) { + log.info("Обновление пользователя с id {}", id); + User existing = userRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Пользователь не найден")); + + if (userDto.getName() != null) { + existing.setName(userDto.getName()); + } + if (userDto.getEmail() != null) { + existing.setEmail(userDto.getEmail()); + } + + User updated = userRepository.update(existing); + return UserMapper.toUserDto(updated); + } + + @Override + public void delete(Long id) { + log.info("Удаление пользователя с id {}", id); + userRepository.delete(id); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 51c51801..e1ee733a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -8,6 +8,7 @@ logging.level.org.springframework.transaction.interceptor=TRACE logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG # TODO Append connection to Postgres DB +# TODO Append connection to DB #spring.datasource.driverClassName #spring.datasource.url #spring.datasource.username