Merge pull request #11 from omriza5/feature/deploy-github-workflow

Remove outdated API and UI testing workflows
This commit is contained in:
omriza5 2025-09-21 12:05:12 +03:00 committed by GitHub
commit 00ca9772c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 312 additions and 120 deletions

200
.github/workflows/e2e-testing.yml vendored Normal file
View file

@ -0,0 +1,200 @@
name: Deploy testing environment to EC2
on:
pull_request:
branches:
- main
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
outputs:
wekan_image_tag: ${{ steps.docker_image_build.outputs.tag }}
steps:
- name: Checkout repository(omriza5/wekan)
uses: actions/checkout@v4
- name: Build and push docker image
id: docker_image_build
run: |
# Login to DockerHub
echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
# Use short commit SHA (first 7 characters)
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
TAG="${SHORT_SHA}-$(date +%Y%m%d-%H%M%S)"
echo "tag=$TAG" >> $GITHUB_OUTPUT
docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/wekan:$TAG .
docker push ${{ secrets.DOCKERHUB_USERNAME }}/wekan:$TAG
# Save the tag for later steps
echo "WEKAN_IMAGE_TAG=$TAG" >> $GITHUB_ENV
- name: Create .env file
run: |
echo "WEKAN_IMAGE=omriza5/wekan:${WEKAN_IMAGE_TAG}" >> .env
- name: Copy .env file to EC2
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.WEKAN_EC2_HOST_IP }}
username: ubuntu
key: ${{ secrets.EC2_SSH_KEY }}
source: ".env"
target: "/home/ubuntu/"
- name: Copy docker-compose file to EC2
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.WEKAN_EC2_HOST_IP }}
username: ubuntu
key: ${{ secrets.EC2_SSH_KEY }}
source: "docker-compose.yml"
target: "/home/ubuntu/"
- name: Deploy to EC2
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.WEKAN_EC2_HOST_IP }}
username: ubuntu
key: ${{ secrets.EC2_SSH_KEY }}
script: |
# Stop and remove containers with volumes
sudo docker compose down -v || true
# Clean up everything including named volumes
sudo docker volume rm $(sudo docker volume ls -q) 2>/dev/null || true
sudo docker stop $(sudo docker ps -aq) 2>/dev/null || true
sudo docker rm $(sudo docker ps -aq) 2>/dev/null || true
# Remove all images to free space
sudo docker rmi $(sudo docker images -q) 2>/dev/null || true
# Clean up networks (volumes already removed above)
sudo docker network prune -f || true
echo "${{ secrets.DOCKERHUB_PASSWORD }}" | sudo docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
sudo docker compose pull
sudo docker compose up -d
API-tests:
needs: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v4
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Create test user via Database
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.WEKAN_EC2_HOST_IP }}
username: ubuntu
key: ${{ secrets.EC2_SSH_KEY }}
script: |
# Wait for Wekan to be fully ready
echo "Waiting for Wekan to start..."
for i in {1..24}; do
if curl -s http://localhost > /dev/null 2>&1; then
echo "Wekan is responding!"
break
fi
echo "Waiting... (attempt $i/24)"
sleep 5
done
# Create user directly in database with the exact structure from browser
echo "Creating test user directly in database..."
sudo docker exec wekan-db mongosh wekan --eval '
// Remove existing user first
db.users.deleteMany({username: "omriza5"});
// Create user with exact structure from browser
const result = db.users.insertOne({
_id: "omriza5_" + new Date().getTime(),
createdAt: new Date(),
services: {
password: {
bcrypt: "$2b$10$v9266B4sMuTCOgPsnIPibuxKoUwELIqPvTn7GQqGvvVibAEsmphsm"
},
email: {
verificationTokens: [
{
token: "token_" + Math.random().toString(36).substring(2),
address: "omriza5@gmail.com",
when: new Date()
}
]
}
},
username: "omriza5",
emails: [{ address: "omriza5@gmail.com", verified: false }],
isAdmin: true,
modifiedAt: new Date(),
profile: {
boardView: "board-view-swimlanes",
listSortBy: "-modifiedAt",
templatesBoardId: "",
cardTemplatesSwimlaneId: "",
listTemplatesSwimlaneId: "",
boardTemplatesSwimlaneId: "",
listWidths: {},
listConstraints: {},
autoWidthBoards: {},
swimlaneHeights: {},
keyboardShortcuts: false,
verticalScrollbars: true,
showWeekOfYear: true
},
authenticationMethod: "password",
sessionData: {}
});
if (result.acknowledged) {
print("User omriza5 created successfully");
} else {
print("Failed to create user");
}
' || echo "Failed to execute MongoDB command"
# Verify user was created
echo "Verifying user creation..."
sudo docker exec wekan-db mongosh wekan --eval 'db.users.findOne({username: "omriza5"}, {username: 1, emails: 1, isAdmin: 1})' || echo "User verification failed"
# Verify login works
echo "Testing login..."
LOGIN_RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" \
-H "Content-type:application/json" \
-X POST http://localhost/users/login \
-d '{"username":"omriza5","password":"123456"}')
LOGIN_CODE=$(echo $LOGIN_RESPONSE | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
if [[ "$LOGIN_CODE" == "200" ]]; then
echo "Login test successful"
else
echo "Login test failed (Code: $LOGIN_CODE)"
echo "Response: $(echo $LOGIN_RESPONSE | sed -e 's/HTTPSTATUS:.*//g')"
fi
- name: Run API tests
env:
BASE_URL: ${{ secrets.WEKAN_URL }}
run: |
pytest --maxfail=5 --disable-warnings -v

1
.gitignore vendored
View file

@ -58,3 +58,4 @@ pip-delete-this-directory.txt
.coverage .coverage
htmlcov/ htmlcov/
*.pem *.pem
*.env

View file

@ -8,13 +8,15 @@ services:
- wekan-tier - wekan-tier
expose: expose:
- 27017 - 27017
ports:
- 27017:27017
volumes: volumes:
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- wekan-db:/data/db - wekan-db:/data/db
- wekan-db-dump:/dump - wekan-db-dump:/dump
wekan: wekan:
image: ghcr.io/wekan/wekan:latest image: ${WEKAN_IMAGE}
container_name: wekan-app container_name: wekan-app
restart: always restart: always
networks: networks:
@ -33,6 +35,9 @@ services:
- BIGEVENTS_PATTERN=NONE - BIGEVENTS_PATTERN=NONE
- BROWSER_POLICY_ENABLED=true - BROWSER_POLICY_ENABLED=true
- LDAP_BACKGROUND_SYNC_INTERVAL='' - LDAP_BACKGROUND_SYNC_INTERVAL=''
- ACCOUNTS_LOCKOUT_UNKNOWN_USERS=false
- ACCOUNTS_REGISTRATION_VERIFY_EMAIL=false
- DISABLE_REGISTRATION=false
depends_on: depends_on:
- wekandb - wekandb
volumes: volumes:

View file

@ -546,9 +546,9 @@ Users.attachSchema(
Users.allow({ Users.allow({
update(userId, doc) { update(userId, doc) {
const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser(); const user =
if (user?.isAdmin) ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser();
return true; if (user?.isAdmin) return true;
if (!user) { if (!user) {
return false; return false;
} }
@ -583,12 +583,14 @@ Users.allow({
// Non-Admin users can not change to Admin // Non-Admin users can not change to Admin
Users.deny({ Users.deny({
update(userId, board, fieldNames) { update(userId, board, fieldNames) {
return _.contains(fieldNames, 'isAdmin') && !ReactiveCache.getCurrentUser().isAdmin; return (
_.contains(fieldNames, 'isAdmin') &&
!ReactiveCache.getCurrentUser().isAdmin
);
}, },
fetch: [], fetch: [],
}); });
// Search a user in the complete server database by its name, username or emails adress. This // Search a user in the complete server database by its name, username or emails adress. This
// is used for instance to add a new user to a board. // is used for instance to add a new user to a board.
UserSearchIndex = new Index({ UserSearchIndex = new Index({
@ -714,7 +716,7 @@ Users.helpers({
orgIdsUserBelongs() { orgIdsUserBelongs() {
let ret = ''; let ret = '';
if (this.orgs) { if (this.orgs) {
ret = this.orgs.map(org => org.orgId).join(','); ret = this.orgs.map((org) => org.orgId).join(',');
} }
return ret; return ret;
}, },
@ -732,7 +734,7 @@ Users.helpers({
teamIdsUserBelongs() { teamIdsUserBelongs() {
let ret = ''; let ret = '';
if (this.teams) { if (this.teams) {
ret = this.teams.map(team => team.teamId).join(','); ret = this.teams.map((team) => team.teamId).join(',');
} }
return ret; return ret;
}, },
@ -801,7 +803,7 @@ Users.helpers({
}, },
getListWidths() { getListWidths() {
const { listWidths = {}, } = this.profile || {}; const { listWidths = {} } = this.profile || {};
return listWidths; return listWidths;
}, },
getListWidth(boardId, listId) { getListWidth(boardId, listId) {
@ -888,8 +890,13 @@ Users.helpers({
const notification = notifications[index]; const notification = notifications[index];
// this preserves their db sort order for editing // this preserves their db sort order for editing
notification.dbIndex = index; notification.dbIndex = index;
if (!notification.activityObj && typeof(notification.activity) === 'string') { if (
notification.activityObj = ReactiveMiniMongoIndex.getActivityWithId(notification.activity); !notification.activityObj &&
typeof notification.activity === 'string'
) {
notification.activityObj = ReactiveMiniMongoIndex.getActivityWithId(
notification.activity,
);
} }
} }
// newest first. don't use reverse() because it changes the array inplace, so sometimes the array is reversed twice and oldest items at top again // newest first. don't use reverse() because it changes the array inplace, so sometimes the array is reversed twice and oldest items at top again
@ -1360,11 +1367,13 @@ if (Meteor.isServer) {
check(userTeamsArray, Array); check(userTeamsArray, Array);
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176 // Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
// Thanks to mc-marcy and xet7 ! // Thanks to mc-marcy and xet7 !
if (fullname.includes('/') || if (
username.includes('/') || fullname.includes('/') ||
email.includes('/') || username.includes('/') ||
initials.includes('/')) { email.includes('/') ||
return false; initials.includes('/')
) {
return false;
} }
if (ReactiveCache.getCurrentUser()?.isAdmin) { if (ReactiveCache.getCurrentUser()?.isAdmin) {
const nUsersWithUsername = ReactiveCache.getUsers({ const nUsersWithUsername = ReactiveCache.getUsers({
@ -1408,9 +1417,8 @@ if (Meteor.isServer) {
check(userId, String); check(userId, String);
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176 // Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
// Thanks to mc-marcy and xet7 ! // Thanks to mc-marcy and xet7 !
if (username.includes('/') || if (username.includes('/') || userId.includes('/')) {
userId.includes('/')) { return false;
return false;
} }
if (ReactiveCache.getCurrentUser()?.isAdmin) { if (ReactiveCache.getCurrentUser()?.isAdmin) {
const nUsersWithUsername = ReactiveCache.getUsers({ const nUsersWithUsername = ReactiveCache.getUsers({
@ -1432,9 +1440,8 @@ if (Meteor.isServer) {
check(username, String); check(username, String);
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176 // Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
// Thanks to mc-marcy and xet7 ! // Thanks to mc-marcy and xet7 !
if (username.includes('/') || if (username.includes('/') || email.includes('/')) {
email.includes('/')) { return false;
return false;
} }
if (ReactiveCache.getCurrentUser()?.isAdmin) { if (ReactiveCache.getCurrentUser()?.isAdmin) {
if (Array.isArray(email)) { if (Array.isArray(email)) {
@ -1472,10 +1479,12 @@ if (Meteor.isServer) {
check(userId, String); check(userId, String);
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176 // Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
// Thanks to mc-marcy and xet7 ! // Thanks to mc-marcy and xet7 !
if (username.includes('/') || if (
email.includes('/') || username.includes('/') ||
userId.includes('/')) { email.includes('/') ||
return false; userId.includes('/')
) {
return false;
} }
if (ReactiveCache.getCurrentUser()?.isAdmin) { if (ReactiveCache.getCurrentUser()?.isAdmin) {
if (Array.isArray(email)) { if (Array.isArray(email)) {
@ -1498,9 +1507,8 @@ if (Meteor.isServer) {
check(userId, String); check(userId, String);
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176 // Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
// Thanks to mc-marcy and xet7 ! // Thanks to mc-marcy and xet7 !
if (email.includes('/') || if (email.includes('/') || userId.includes('/')) {
userId.includes('/')) { return false;
return false;
} }
if (ReactiveCache.getCurrentUser()?.isAdmin) { if (ReactiveCache.getCurrentUser()?.isAdmin) {
Users.update(userId, { Users.update(userId, {
@ -1520,9 +1528,8 @@ if (Meteor.isServer) {
check(userId, String); check(userId, String);
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176 // Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
// Thanks to mc-marcy and xet7 ! // Thanks to mc-marcy and xet7 !
if (initials.includes('/') || if (initials.includes('/') || userId.includes('/')) {
userId.includes('/')) { return false;
return false;
} }
if (ReactiveCache.getCurrentUser()?.isAdmin) { if (ReactiveCache.getCurrentUser()?.isAdmin) {
Users.update(userId, { Users.update(userId, {
@ -1538,9 +1545,8 @@ if (Meteor.isServer) {
check(boardId, String); check(boardId, String);
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176 // Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
// Thanks to mc-marcy and xet7 ! // Thanks to mc-marcy and xet7 !
if (username.includes('/') || if (username.includes('/') || boardId.includes('/')) {
boardId.includes('/')) { return false;
return false;
} }
const inviter = ReactiveCache.getCurrentUser(); const inviter = ReactiveCache.getCurrentUser();
const board = ReactiveCache.getBoard(boardId); const board = ReactiveCache.getBoard(boardId);
@ -1586,9 +1592,8 @@ if (Meteor.isServer) {
username = email.substring(0, posAt); username = email.substring(0, posAt);
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176 // Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
// Thanks to mc-marcy and xet7 ! // Thanks to mc-marcy and xet7 !
if (username.includes('/') || if (username.includes('/') || email.includes('/')) {
email.includes('/')) { return false;
return false;
} }
const newUserId = Accounts.createUser({ const newUserId = Accounts.createUser({
username, username,
@ -1618,51 +1623,52 @@ if (Meteor.isServer) {
subBoard.addMember(user._id); subBoard.addMember(user._id);
user.addInvite(subBoard._id); user.addInvite(subBoard._id);
} }
} try { }
const fullName = try {
inviter.profile !== undefined && const fullName =
inviter.profile.fullname !== undefined inviter.profile !== undefined &&
? inviter.profile.fullname inviter.profile.fullname !== undefined
: ''; ? inviter.profile.fullname
const userFullName = : '';
user.profile !== undefined && user.profile.fullname !== undefined const userFullName =
? user.profile.fullname user.profile !== undefined && user.profile.fullname !== undefined
: ''; ? user.profile.fullname
const params = { : '';
user: const params = {
userFullName != '' user:
? userFullName + ' (' + user.username + ' )' userFullName != ''
: user.username, ? userFullName + ' (' + user.username + ' )'
inviter: : user.username,
fullName != '' inviter:
? fullName + ' (' + inviter.username + ' )' fullName != ''
: inviter.username, ? fullName + ' (' + inviter.username + ' )'
board: board.title, : inviter.username,
url: board.absoluteUrl(), board: board.title,
}; url: board.absoluteUrl(),
// Get the recipient user's language preference for the email };
const lang = user.getLanguage(); // Get the recipient user's language preference for the email
const lang = user.getLanguage();
// Add code to send invitation with EmailLocalization // Add code to send invitation with EmailLocalization
if (typeof EmailLocalization !== 'undefined') { if (typeof EmailLocalization !== 'undefined') {
EmailLocalization.sendEmail({ EmailLocalization.sendEmail({
to: user.emails[0].address, to: user.emails[0].address,
from: Accounts.emailTemplates.from, from: Accounts.emailTemplates.from,
subject: 'email-invite-subject', subject: 'email-invite-subject',
text: 'email-invite-text', text: 'email-invite-text',
params: params, params: params,
language: lang, language: lang,
userId: user._id userId: user._id,
}); });
} else { } else {
// Fallback if EmailLocalization is not available // Fallback if EmailLocalization is not available
Email.send({ Email.send({
to: user.emails[0].address, to: user.emails[0].address,
from: Accounts.emailTemplates.from, from: Accounts.emailTemplates.from,
subject: TAPi18n.__('email-invite-subject', params, lang), subject: TAPi18n.__('email-invite-subject', params, lang),
text: TAPi18n.__('email-invite-text', params, lang), text: TAPi18n.__('email-invite-text', params, lang),
}); });
} }
} catch (e) { } catch (e) {
throw new Meteor.Error('email-fail', e.message); throw new Meteor.Error('email-fail', e.message);
} }
@ -1688,7 +1694,9 @@ if (Meteor.isServer) {
}, },
isImpersonated(userId) { isImpersonated(userId) {
check(userId, String); check(userId, String);
const isImpersonated = ReactiveCache.getImpersonatedUser({ userId: userId }); const isImpersonated = ReactiveCache.getImpersonatedUser({
userId: userId,
});
return isImpersonated; return isImpersonated;
}, },
setUsersTeamsTeamDisplayName(teamId, teamDisplayName) { setUsersTeamsTeamDisplayName(teamId, teamDisplayName) {
@ -1760,15 +1768,12 @@ if (Meteor.isServer) {
}, },
]; ];
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176 // Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
// Thanks to mc-marcy and xet7 ! // Thanks to mc-marcy and xet7 !
if (user.username.includes('/') || if (user.username.includes('/') || email.includes('/')) {
email.includes('/')) { return false;
return false;
} }
const initials = user.services.oidc.fullname const initials = user.services.oidc.fullname
.split(/\s+/) .split(/\s+/)
.reduce((memo, word) => { .reduce((memo, word) => {
@ -1817,7 +1822,8 @@ if (Meteor.isServer) {
return user; return user;
} }
const disableRegistration = ReactiveCache.getCurrentSetting().disableRegistration; const disableRegistration =
ReactiveCache.getCurrentSetting().disableRegistration;
// If this is the first Authentication by the ldap and self registration disabled // If this is the first Authentication by the ldap and self registration disabled
if (disableRegistration && options && options.ldap) { if (disableRegistration && options && options.ldap) {
user.authenticationMethod = 'ldap'; user.authenticationMethod = 'ldap';
@ -1909,8 +1915,13 @@ if (Meteor.isServer) {
modifiedAt: -1, modifiedAt: -1,
}); });
// Avatar URLs from CollectionFS to Meteor-Files, at users collection avatarUrl field: // Avatar URLs from CollectionFS to Meteor-Files, at users collection avatarUrl field:
Users.find({ "profile.avatarUrl": { $regex: "/cfs/files/avatars/" } }).forEach(function (doc) { Users.find({
doc.profile.avatarUrl = doc.profile.avatarUrl.replace("/cfs/files/avatars/", "/cdn/storage/avatars/"); 'profile.avatarUrl': { $regex: '/cfs/files/avatars/' },
}).forEach(function (doc) {
doc.profile.avatarUrl = doc.profile.avatarUrl.replace(
'/cfs/files/avatars/',
'/cdn/storage/avatars/',
);
// Try to fix Users.save is not a fuction, by commenting it out: // Try to fix Users.save is not a fuction, by commenting it out:
//Users.save(doc); //Users.save(doc);
}); });
@ -2133,7 +2144,8 @@ if (Meteor.isServer) {
} }
//invite user to corresponding boards //invite user to corresponding boards
const disableRegistration = ReactiveCache.getCurrentSetting().disableRegistration; const disableRegistration =
ReactiveCache.getCurrentSetting().disableRegistration;
// If ldap, bypass the inviation code if the self registration isn't allowed. // If ldap, bypass the inviation code if the self registration isn't allowed.
// TODO : pay attention if ldap field in the user model change to another content ex : ldap field to connection_type // TODO : pay attention if ldap field in the user model change to another content ex : ldap field to connection_type
if (doc.authenticationMethod !== 'ldap' && disableRegistration) { if (doc.authenticationMethod !== 'ldap' && disableRegistration) {

View file

@ -25,6 +25,7 @@ class TestLogin:
assert response.status_code == 200 assert response.status_code == 200
json_response = response.json() json_response = response.json()
assert 'token' in json_response assert 'token' in json_response
assert isinstance(json_response['token'], str) assert isinstance(json_response['token'], str)
assert len(json_response['token']) > 0 assert len(json_response['token']) > 0
@ -43,6 +44,7 @@ class TestLogin:
assert response.status_code in [400, 401, 404] assert response.status_code in [400, 401, 404]
json_response = response.json() json_response = response.json()
assert 'error' in json_response assert 'error' in json_response
assert json_response['error'] == 'not-found' assert json_response['error'] == 'not-found'
assert 'reason' in json_response assert 'reason' in json_response

View file

@ -13,7 +13,6 @@ class TestBoard:
'password': '123456' 'password': '123456'
} }
print("🔐 Getting authentication token...")
response = requests.post(f"{base_url}/users/login", data=login_data) response = requests.post(f"{base_url}/users/login", data=login_data)
if response.status_code == 200: if response.status_code == 200:
@ -22,38 +21,16 @@ class TestBoard:
# Store token and user info in class # Store token and user info in class
request.cls.auth_token = json_response['token'] request.cls.auth_token = json_response['token']
request.cls.user_id = json_response.get('id', '') request.cls.user_id = json_response.get('id', '')
print(f"✅ Token obtained: {request.cls.auth_token[:20]}...")
print(f"✅ User ID obtained: {request.cls.user_id[:20]}...")
else: else:
request.cls.auth_token = None request.cls.auth_token = None
print(f"❌ Login failed: {json_response}")
else: else:
request.cls.auth_token = None request.cls.auth_token = None
print(f"❌ Login request failed: {response.status_code}")
def test_health_check(self): def test_health_check(self):
"""Test basic health check""" """Test basic health check"""
response = requests.get(f"{base_url}") response = requests.get(f"{base_url}")
assert response.status_code == 200 assert response.status_code == 200
def test_get_user_boards(self):
"""Test getting information about boards of user"""
if not self.auth_token:
pytest.skip("No authentication token available")
response = requests.get(
f"{base_url}/api/users/{self.user_id}/boards",
headers={"Authorization": f"Bearer {self.auth_token}"}
)
assert response.status_code == 200
# Should return a list of boards
boards_data = response.json()
assert isinstance(boards_data, list), "Response should be a list of boards"
assert "title" in boards_data[0], "First board object should have a 'title' key"
def test_create_board_minimal(self): def test_create_board_minimal(self):
"""Test creating a board with minimal required fields""" """Test creating a board with minimal required fields"""
if not self.auth_token: if not self.auth_token:
@ -186,9 +163,6 @@ class TestBoard:
data=board_data data=board_data
) )
print(f"🚫 Unauthorized creation status: {response.status_code}")
print(f"🚫 Unauthorized response: {response.text[:200]}")
# Should require authentication # Should require authentication
assert response.status_code in [400, 401, 403], "Should require authentication" assert response.status_code in [400, 401, 403], "Should require authentication"
@ -202,7 +176,5 @@ class TestBoard:
headers={"Authorization": f"Bearer {self.auth_token}"} headers={"Authorization": f"Bearer {self.auth_token}"}
) )
print(f"📋 Get boards API status: {response.json()}")
# Should work with authentication # Should work with authentication
assert response.status_code in [200, 204] assert response.status_code in [200, 204]