From 5643477b03a2315b4d41a3c84b80e04753a7fddc Mon Sep 17 00:00:00 2001
From: Liiizak <144727441+Liiizak@users.noreply.github.com>
Date: Wed, 6 Nov 2024 11:06:36 +0300
Subject: [PATCH] Updated CMakeLists.txt, CI and userver submodule (#3)

---
 .github/workflows/ci.yml         |  38 +++++--
 .github/workflows/docker.yaml    |   5 +
 CMakeLists.txt                   |  34 ++++--
 Makefile                         | 179 +++++++++++++------------------
 configs/config_vars.docker.yaml  |   4 +-
 configs/config_vars.yaml         |   5 +-
 configs/config_vars_testing.yaml |   2 +-
 configs/static_config.yaml.in    |  17 ++-
 docker-compose.yml               |   6 +-
 tests/genStaticTest.py           |   4 +-
 tests/genTest.py                 |   4 +-
 tests/helpers/models.py          |  10 +-
 tests/run_as_user.sh             |  18 ++++
 third_party/userver              |   2 +-
 14 files changed, 183 insertions(+), 145 deletions(-)
 create mode 100755 tests/run_as_user.sh

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d17d0c3..850c8e5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,24 +15,24 @@ jobs:
             fail-fast: false
             matrix:
                 include:
-                  - os: ubuntu-20.04
+                  - os: ubuntu-22.04
                     make: test-debug
-                    info: g++-9 + test-debug
+                    info: g++-11 + test-debug
 
-                  - os: ubuntu-20.04
+                  - os: ubuntu-22.04
                     make: test-release
-                    info: g++-9 + test-release
+                    info: g++-11 + test-release
 
         name: '${{matrix.os}}: ${{matrix.info}}'
         runs-on: ${{matrix.os}}
 
         steps:
-          - uses: actions/checkout@v2
+          - uses: actions/checkout@v4
             with:
                 submodules: true
 
           - name: Reuse ccache directory
-            uses: actions/cache@v2
+            uses: actions/cache@v4
             with:
                 path: ~/.ccache
                 key: '${{matrix.os}} ${{matrix.info}} ccache-dir ${{github.ref}} run-${{github.run_number}}'
@@ -43,13 +43,32 @@ jobs:
           - name: Install packages
             run: |
                 sudo apt update
+                sudo apt install --allow-downgrades -y postgresql $(cat third_party/userver/scripts/docs/en/deps/${{matrix.os}}.md | tr '\n' ' ')
                 sudo apt install --allow-downgrades -y pep8 $(cat third_party/userver/scripts/docs/en/deps/${{matrix.os}}.md | tr '\n' ' ')
+   
+          - name: Reinstall postgres 14
+            run: |
+                sudo apt purge libpq5 libpq-dev postgresql-*
+                sudo apt install -y postgresql-14 postgresql-client-14 postgresql-server-dev-14
+
 
           - name: Setup ccache
             run: |
                 ccache -M 2.0GB
                 ccache -s
 
+          - name: Run postgresql
+            if: matrix.make == 'test-release'
+            run: |
+                pg_lsclusters
+                sudo -u postgres psql --command="CREATE USER realmedium_sample_user PASSWORD 'password'" --command="\du"
+                sudo -u postgres createdb --owner=realmedium_sample_user realmedium_db-1
+
+          - name: Run migrations
+            if: matrix.make == 'test-release'
+            run: |
+                PGPASSWORD=password psql 'postgresql://realmedium_sample_user@localhost:5432/realmedium_db-1' -f ./postgresql/schemas/db-1.sql
+
           - name: Run ${{matrix.make}}
             run: |
                 make ${{matrix.make}}
@@ -62,8 +81,11 @@ jobs:
 
           - name: Test run after install
             if: matrix.make == 'test-release'
-            run: |
-              ./local_installation/bin/realmedium_sample --config=./local_installation/etc/realmedium_sample/static_config.yaml &
+            run: >-
+               ./local_installation/bin/realmedium_sample
+               --config=./local_installation/etc/realmedium_sample/static_config.yaml
+               --config_vars=./local_installation/etc/realmedium_sample/config_vars.yaml
+               &
 
           - name: Check work run service
             if: matrix.make == 'test-release'
diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml
index 7da2b86..9201371 100644
--- a/.github/workflows/docker.yaml
+++ b/.github/workflows/docker.yaml
@@ -28,6 +28,11 @@ jobs:
                     ccache-dir-${{github.ref}}_run-
                     ccache-
 
+          - name: Install docker-compose
+            run: |
+                sudo apt update
+                sudo apt install --allow-downgrades -y docker-compose
+
           - name: Setup ccache
             run: docker-compose run --rm realmedium-sample bash -c 'ccache -M 4.0GB && ccache -s'
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d751cbe..0398d3c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,10 +1,28 @@
 cmake_minimum_required(VERSION 3.12)
 project(realmedium_sample CXX)
 
-include(third_party/userver/cmake/SetupEnvironment.cmake)
-include(GNUInstallDirs)
 
-add_subdirectory(third_party/userver)
+# Adding userver dependency
+find_package(userver COMPONENTS core postgresql QUIET)
+if(NOT userver_FOUND)  # Fallback to subdirectory usage
+    # Enable userver libraries that are needed in this project
+    set(USERVER_FEATURE_POSTGRESQL ON CACHE BOOL "" FORCE)
+
+    # Compatibility mode: some systems don't support these features
+    set(USERVER_FEATURE_CRYPTOPP_BLAKE2 OFF CACHE BOOL "" FORCE)
+    set(USERVER_FEATURE_GRPC_CHANNELZ OFF CACHE BOOL "" FORCE)
+    set(USERVER_FEATURE_REDIS_HI_MALLOC ON CACHE BOOL "" FORCE)
+
+    if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/userver)
+        message(STATUS "Using userver framework from third_party/userver")
+        add_subdirectory(third_party/userver)
+    else()
+        message(FATAL_ERROR "Either install the userver or provide a path to it")
+    endif()
+endif()
+
+userver_setup_environment()
+
 
 set(CPP_JWT_BUILD_TESTS OFF)
 set(CPP_JWT_BUILD_EXAMPLES OFF)
@@ -98,7 +116,7 @@ add_library(${PROJECT_NAME}_objs OBJECT
 )
 
 include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
-target_link_libraries(${PROJECT_NAME}_objs PUBLIC userver-core userver-postgresql)
+target_link_libraries(${PROJECT_NAME}_objs PUBLIC userver::core userver::postgresql)
 
 target_include_directories(${PROJECT_NAME}_objs PUBLIC cpp-jwt)
 target_link_libraries(${PROJECT_NAME}_objs PUBLIC cpp-jwt)
@@ -118,12 +136,16 @@ add_executable(${PROJECT_NAME}_unittest
 	src/validators/validator_test.cpp
 )
 
-target_link_libraries(${PROJECT_NAME}_unittest PRIVATE ${PROJECT_NAME}_objs userver-utest)
+target_link_libraries(${PROJECT_NAME}_unittest PRIVATE ${PROJECT_NAME}_objs userver::utest)
 add_google_tests(${PROJECT_NAME}_unittest)
 
 # Functional Tests
 add_subdirectory(tests)
 
+
+# Install
+include(GNUInstallDirs)
+
 if(DEFINED ENV{PREFIX})
     message(STATUS "Set install prefix: $ENV{PREFIX}")
     file(TO_CMAKE_PATH "$ENV{PREFIX}" PREFIX_PATH)
@@ -136,7 +158,7 @@ set(CONFIG_JWT ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_SYSCONFDIR}/${PROJECT_NAM
 
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configs/static_config.yaml.in ${CMAKE_CURRENT_SOURCE_DIR}/configs/static_config.yaml)
 
-FILE(GLOB CONFIGS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/configs/*.yaml ${CMAKE_CURRENT_SOURCE_DIR}/configs/*.json)
+file(GLOB CONFIGS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/configs/*.yaml ${CMAKE_CURRENT_SOURCE_DIR}/configs/*.json)
 
 install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${PROJECT_NAME})
 install(FILES ${CONFIGS_FILES} DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/${PROJECT_NAME} COMPONENT ${PROJECT_NAME})
diff --git a/Makefile b/Makefile
index c26d1cb..b486376 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,10 @@
 CMAKE_COMMON_FLAGS ?= -DUSERVER_OPEN_SOURCE_BUILD=1 -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
-
 CMAKE_DEBUG_FLAGS ?= -DUSERVER_SANITIZE='addr ub'
 CMAKE_RELEASE_FLAGS ?=
 CMAKE_OS_FLAGS ?= -DUSERVER_FEATURE_CRYPTOPP_BLAKE2=0 -DUSERVER_FEATURE_REDIS_HI_MALLOC=1
 NPROCS ?= $(shell nproc)
 CLANG_FORMAT ?= clang-format
+DOCKER_COMPOSE ?= docker-compose
 
 ifeq ($(KERNEL),Darwin)
 CMAKE_COMMON_FLAGS += -DUSERVER_NO_WERROR=1 -DUSERVER_CHECK_PACKAGE_VERSIONS=0 \
@@ -17,124 +17,91 @@ CMAKE_COMMON_FLAGS += -DUSERVER_NO_WERROR=1 -DUSERVER_CHECK_PACKAGE_VERSIONS=0 \
 endif
 
 
-# NOTE: use Makefile.local for customization
+# NOTE: use Makefile.local to override the options defined above.
 -include Makefile.local
 
+CMAKE_DEBUG_FLAGS += -DCMAKE_BUILD_TYPE=Debug $(CMAKE_COMMON_FLAGS)
+CMAKE_RELEASE_FLAGS += -DCMAKE_BUILD_TYPE=Release $(CMAKE_COMMON_FLAGS)
+
+.PHONY: all
 all: test-debug test-release
 
-# Debug cmake configuration
-build_debug/Makefile:
-	@git submodule update --init
-	@mkdir -p build_debug
-	@cd build_debug && \
-      cmake -DCMAKE_BUILD_TYPE=Debug $(CMAKE_COMMON_FLAGS) $(CMAKE_DEBUG_FLAGS) $(CMAKE_OS_FLAGS) $(CMAKE_OPTIONS) ..
-
-# Release cmake configuration
-build_release/Makefile:
-	@git submodule update --init
-	@mkdir -p build_release
-	@cd build_release && \
-      cmake -DCMAKE_BUILD_TYPE=Release $(CMAKE_COMMON_FLAGS) $(CMAKE_RELEASE_FLAGS) $(CMAKE_OS_FLAGS) $(CMAKE_OPTIONS) ..
-
-# build using cmake
-build-impl-%: build_%/Makefile
-	@cmake --build build_$* -j $(NPROCS) --target realmedium_sample
-
-# test
-test-impl-%: build-impl-%
-	@cmake --build build_$* -j $(NPROCS) --target realmedium_sample_unittest
-	@cd build_$* && ((test -t 1 && GTEST_COLOR=1 PYTEST_ADDOPTS="--color=yes" ctest -V) || ctest -V)
-	@pep8 tests
-
-# testsuite service runner
-service-impl-start-%: build-impl-%
-	@cd ./build_$* && $(MAKE) start-realmedium-sample
-
-# clean
-clean-impl-%:
-	cd build_$* && $(MAKE) clean
-
-# dist-clean
-.PHONY: dist-clean
-dist-clean:
-	@rm -rf build_*
-	@rm -f ./configs/static_config.yaml
+# Run cmake
+.PHONY: cmake-debug
+cmake-debug:
+	cmake -B build_debug $(CMAKE_DEBUG_FLAGS)
 
-# format
-.PHONY: format
-format:
-	@find src -name '*pp' -type f | xargs $(CLANG_FORMAT) -i
-	@find tests -name '*.py' -type f | xargs autopep8 -i
-
-.PHONY: cmake-debug build-debug test-debug clean-debug cmake-release build-release test-release clean-release install install-debug docker-cmake-debug docker-build-debug docker-test-debug docker-clean-debug docker-cmake-release docker-build-release docker-test-release docker-clean-release docker-install docker-install-debug docker-start-service-debug docker-start-service docker-clean-data
-
-install-debug: build-debug
-	@cd build_debug && \
-		cmake --install . -v --component realmedium_sample
-
-install: build-release
-	@cd build_release && \
-		cmake --install . -v --component realmedium_sample
-
-# Hide target, use only in docker environment
---debug-start-in-docker: install
-	@ulimit -n 100000
-	@sed -i 's/config_vars.yaml/config_vars.docker.yaml/g' /home/user/.local/etc/realmedium_sample/static_config.yaml
-	@psql 'postgresql://user:password@service-postgres:5432/realmedium_db-1' -f ./postgresql/schemas/db-1.sql
-	@/home/user/.local/bin/realmedium_sample \
-		--config /home/user/.local/etc/realmedium_sample/static_config.yaml
-
-# Hide target, use only in docker environment
---debug-start-in-docker-debug: install-debug
-	@sed -i 's/config_vars.yaml/config_vars.docker.yaml/g' /home/user/.local/etc/realmedium_sample/static_config.yaml
-	@psql 'postgresql://user:password@service-postgres:5432/realmedium_db-1' -f ./postgresql/schemas/db-1.sql
-	@/home/user/.local/bin/realmedium_sample \
-		--config /home/user/.local/etc/realmedium_sample/static_config.yaml
-
-# Start targets makefile in docker enviroment
-docker-impl-%:
-	docker-compose run --rm realmedium-sample make $*
-
-# Build and runs service in docker environment
-docker-start-service-debug:
-	@docker-compose run -p 8080:8080 --rm realmedium-sample make -- --debug-start-in-docker-debug
-
-# Build and runs service in docker environment
-docker-start-service:
-	@docker-compose run -p 8080:8080 --rm realmedium-sample make -- --debug-start-in-docker
+.PHONY: cmake-release
+cmake-release:
+	cmake -B build_release $(CMAKE_RELEASE_FLAGS)
 
-# Stop docker container and remove PG data
-docker-clean-data:
-	@docker-compose down -v
-	@rm -rf ./.pgdata
+build_debug/CMakeCache.txt: cmake-debug
+build_release/CMakeCache.txt: cmake-release
 
-# Explicitly specifying the targets to help shell with completions
-cmake-debug: build_debug/Makefile
-cmake-release: build_release/Makefile
+# Build using cmake
+.PHONY: build-debug build-release
+build-debug build-release: build-%: build_%/CMakeCache.txt
+	cmake --build build_$* -j $(NPROCS) --target realmedium_sample
 
-build-debug: build-impl-debug
-build-release: build-impl-release
+# Test
+.PHONY: test-debug test-release
+test-debug test-release: test-%: build-%
+	cmake --build build_$* -j $(NPROCS) --target realmedium_sample_unittest
+	cd build_$* && ((test -t 1 && GTEST_COLOR=1 PYTEST_ADDOPTS="--color=yes" ctest -V) || ctest -V)
+	pycodestyle tests
 
-test-debug: test-impl-debug
-test-release: test-impl-release
+# Start the service (via testsuite service runner)
+.PHONY: service-start-debug service-start-release
+service-start-debug service-start-release: service-start-%: build-%
+	cmake --build build_$* -v --target start-realmedium_sample
 
-service-start-debug: service-impl-start-debug
-service-start-release: service-impl-start-release
+# Cleanup data
+.PHONY: clean-debug clean-release
+clean-debug clean-release: clean-%:
+	cmake --build build_$* --target clean
 
-clean-debug: clean-impl-debug
-clean-release: clean-impl-release
+.PHONY: dist-clean
+dist-clean:
+	rm -rf build_*
+	rm -rf tests/__pycache__/
+	rm -rf tests/.pytest_cache/
+
+# Install
+.PHONY: install-debug install-release
+install-debug install-release: install-%: build-%
+	cmake --install build_$* -v --component realmedium_sample
+
+.PHONY: install
+install: install-release
+
+# Format the sources
+.PHONY: format
+format:
+	find src -name '*pp' -type f | xargs $(CLANG_FORMAT) -i
+	find tests -name '*.py' -type f | xargs autopep8 -i
 
-docker-cmake-debug: docker-impl-cmake-debug
-docker-cmake-release: docker-impl-cmake-release
+# Set environment for --in-docker-start
+export DB_CONNECTION := postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@service-postgres:5432/${POSTGRES_DB}
 
-docker-build-debug: docker-impl-build-debug
-docker-build-release: docker-impl-build-release
+# Internal hidden targets that are used only in docker environment
+--in-docker-start-debug --in-docker-start-release: --in-docker-start-%: install-%
+	psql ${DB_CONNECTION} -f ./postgresql/data/initial_data.sql
+	/home/user/.local/bin/realmedium_sample \
+		--config /home/user/.local/etc/realmedium_sample/static_config.yaml \
+		--config_vars /home/user/.local/etc/realmedium_sample/config_vars.docker.yaml
 
-docker-test-debug: docker-impl-test-debug
-docker-test-release: docker-impl-test-release
+# Build and run service in docker environment
+.PHONY: docker-start-service-debug docker-start-service-release
+docker-start-service-debug docker-start-service-release: docker-start-service-%:
+	$(DOCKER_COMPOSE) run -p 8080:8080 --rm realmedium-sample make -- --in-docker-start-$*
 
-docker-clean-debug: docker-impl-clean-debug
-docker-clean-release: docker-impl-clean-release
+# Start targets makefile in docker environment
+.PHONY: docker-cmake-debug docker-build-debug docker-test-debug docker-clean-debug docker-install-debug docker-cmake-release docker-build-release docker-test-release docker-clean-release docker-install-release
+docker-cmake-debug docker-build-debug docker-test-debug docker-clean-debug docker-install-debug docker-cmake-release docker-build-release docker-test-release docker-clean-release docker-install-release: docker-%:
+	$(DOCKER_COMPOSE) run --rm realmedium-sample make $*
 
-docker-install: docker-impl-install
-docker-install-debug: docker-impl-install-debug
+# Stop docker container and remove PG data
+.PHONY: docker-clean-data
+docker-clean-data:
+	$(DOCKER_COMPOSE) down -v
+	rm -rf ./.pgdata
diff --git a/configs/config_vars.docker.yaml b/configs/config_vars.docker.yaml
index f6779b9..fb61013 100644
--- a/configs/config_vars.docker.yaml
+++ b/configs/config_vars.docker.yaml
@@ -1,10 +1,8 @@
 worker-threads: 4
 worker-fs-threads: 2
-logger-level: critical
+logger-level: debug
 
 is_testing: false
 
 server-port: 8080
 
-dbconnection: 'postgresql://user:password@service-postgres:5432/realmedium_db-1'
-
diff --git a/configs/config_vars.yaml b/configs/config_vars.yaml
index 27a6e77..28938f6 100644
--- a/configs/config_vars.yaml
+++ b/configs/config_vars.yaml
@@ -1,10 +1,9 @@
 worker-threads: 4
 worker-fs-threads: 2
-logger-level: error
+logger-level: info
 
 is_testing: false
 
 server-port: 8080
 
-dbconnection: 'postgresql://user:password@service-postgres:5432/realmedium_db-1'
-
+dbconnection: 'postgresql://realmedium_sample_user:password@localhost:5432/realmedium_db-1'
diff --git a/configs/config_vars_testing.yaml b/configs/config_vars_testing.yaml
index 48b0160..3accab3 100644
--- a/configs/config_vars_testing.yaml
+++ b/configs/config_vars_testing.yaml
@@ -6,4 +6,4 @@ is_testing: true
 
 server-port: 8080
 
-dbconnection: 'postgresql://testsuite@localhost:15433/realmedium_db_1'
+dbconnection: 'postgresql://testsuite@localhost:15433/realmedium_db-1'
diff --git a/configs/static_config.yaml.in b/configs/static_config.yaml.in
index 2a1f2c6..b48009e 100644
--- a/configs/static_config.yaml.in
+++ b/configs/static_config.yaml.in
@@ -6,7 +6,7 @@ components_manager:
     coro_pool:
         initial_size: 500             # Preallocate 500 coroutines at startup.
         max_size: 1000                # Do not keep more than 1000 preallocated coroutines.
-
+        
     task_processors:                  # Task processor is an executor for coroutine tasks
 
         main-task-processor:          # Make a task processor for CPU-bound couroutine tasks.
@@ -35,10 +35,15 @@ components_manager:
         tracer:                              # Component that helps to trace execution times and requests in logs.
             service-name: realmedium   # "You know. You all know exactly who I am. Say my name. " (c)
 
-        dynamic-config:                      # Dynamic config storage options, do nothing
-            fs-cache-path: ''
-        dynamic-config-fallbacks:            # Load options from file and push them into the dynamic config storage.
-            fallback-path: @CONFIG_FALLBACK_PATH@
+        # Dynamic config options. Cache is disabled, updates are disabled.
+        dynamic-config:
+            # For most of userver dynamic configs, defaults are used, some are overridden here.
+            # See userver "dynamic config" docs for what configs exist.
+            defaults:
+                HTTP_CLIENT_CONNECTION_POOL_SIZE: 1000
+                POSTGRES_DEFAULT_COMMAND_CONTROL:
+                    network_timeout_ms: 750
+                    statement_timeout_ms: 500
         testsuite-support: {}
 
         articles-cache:
@@ -78,9 +83,11 @@ components_manager:
 
         realmedium-database:
             dbconnection: $dbconnection
+            dbconnection#env: DB_CONNECTION
             blocking_task_processor: fs-task-processor
             dns_resolver: async
             sync-start: true
+            connlimit_mode: manual
 
         handler-get-profile:
             path: /api/profiles/{username}                 
diff --git a/docker-compose.yml b/docker-compose.yml
index 9249cfc..1b7182a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -9,7 +9,7 @@ services:
           - POSTGRES_USER=user
           - POSTGRES_PASSWORD=password
         ports:
-          - 8081:5432
+          - 5432
         volumes:
           - ./postgresql/schemas:/docker-entrypoint-initdb.d
           - ./.pgdata:/var/lib/postgresql/data
@@ -17,7 +17,7 @@ services:
           - postgres
 
     realmedium-sample:
-        image: ghcr.io/userver-framework/ubuntu-userver-build-base:v1
+        image: ghcr.io/userver-framework/ubuntu-22.04-userver-pg:latest
         privileged: true
         environment:
           - POSTGRES_DB=realmedium_db-1
@@ -43,7 +43,7 @@ services:
           - 8080:8080
         working_dir: /realmedium
         entrypoint:
-          - /tools/run_as_user.sh
+          - ./tests/run_as_user.sh
         depends_on:
           - postgres
         networks:
diff --git a/tests/genStaticTest.py b/tests/genStaticTest.py
index 7e7af8a..91463cc 100644
--- a/tests/genStaticTest.py
+++ b/tests/genStaticTest.py
@@ -192,7 +192,7 @@ def fillFavorites(con):
         if curPos + COUNT_FAVORITES > COUNT_OF_ARTICLES:
             curPos = 0
 
-        randArticleIDList = articleIDList[curPos : curPos + COUNT_FAVORITES]
+        randArticleIDList = articleIDList[curPos: curPos + COUNT_FAVORITES]
         curPos += COUNT_FAVORITES
 
         for articleID in randArticleIDList:
@@ -217,7 +217,7 @@ def fillFollowers(con):
         if curPos + COUNT_FOLLOWERS > COUNT_OF_USERS:
             curPos = 0
 
-        randFollowerList = userIDList[curPos : curPos + COUNT_FOLLOWERS]
+        randFollowerList = userIDList[curPos: curPos + COUNT_FOLLOWERS]
         curPos += COUNT_FOLLOWERS
 
         for follower in randFollowerList:
diff --git a/tests/genTest.py b/tests/genTest.py
index 1f7233c..6cdcaea 100644
--- a/tests/genTest.py
+++ b/tests/genTest.py
@@ -529,7 +529,7 @@ def fillFavorites(con):
         if curPos + step > COUNT_OF_ARTICLES:
             curPos = 0
 
-        randArticleIDList = articleIDList[curPos : curPos + step]
+        randArticleIDList = articleIDList[curPos: curPos + step]
         curPos += step
 
         for articleID in randArticleIDList:
@@ -556,7 +556,7 @@ def fillFollowers(con):
         if curPos + step > COUNT_OF_USERS:
             curPos = 0
 
-        randFollowerList = userIDList[curPos : curPos + step]
+        randFollowerList = userIDList[curPos: curPos + step]
         curPos += step
 
         for follower in randFollowerList:
diff --git a/tests/helpers/models.py b/tests/helpers/models.py
index 4412bf3..ec80bf0 100644
--- a/tests/helpers/models.py
+++ b/tests/helpers/models.py
@@ -10,11 +10,11 @@
 
 
 class User(BaseModel):
-    username: str | None = Field(default_factory=fake.user_name)
-    email: str | None = Field(default_factory=fake.email)
-    password: str | None = Field(default_factory=fake.password)
-    bio: str | None = Field(default_factory=fake.paragraph)
-    image: str | None = Field(default_factory=fake.image_url)
+    username: Optional[str] = Field(default_factory=fake.user_name)
+    email: Optional[str] = Field(default_factory=fake.email)
+    password: Optional[str] = Field(default_factory=fake.password)
+    bio: Optional[str] = Field(default_factory=fake.paragraph)
+    image: Optional[str] = Field(default_factory=fake.image_url)
 
 
 class Profile(BaseModel):
diff --git a/tests/run_as_user.sh b/tests/run_as_user.sh
new file mode 100755
index 0000000..5e0c9f1
--- /dev/null
+++ b/tests/run_as_user.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+# Exit on any error and treat unset variables as errors
+set -euo pipefail
+
+DIR_UID="$(stat -c '%u' .)"
+
+if ! id -u user > /dev/null 2> /dev/null; then
+    if [ "$DIR_UID" = "0" ]; then
+        useradd --create-home --no-user-group user
+    else
+        useradd --create-home --no-user-group --uid $DIR_UID user
+    fi
+elif [ "$DIR_UID" != "0" ]; then
+    usermod -u $DIR_UID user
+fi
+
+HOME=/home/user sudo -E -u user "$@"
diff --git a/third_party/userver b/third_party/userver
index ef6cd42..b13acb8 160000
--- a/third_party/userver
+++ b/third_party/userver
@@ -1 +1 @@
-Subproject commit ef6cd42b3fd530b97c609ad5c5487924632b855c
+Subproject commit b13acb8f2468fcfef8b3fce00959b111d5e440ab