Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
be6e19b
Initial plan
Copilot Nov 8, 2025
3e62034
Add test client and basic test infrastructure
Copilot Nov 8, 2025
b63aa96
Fix server initialization and test infrastructure issues
Copilot Nov 8, 2025
371f003
Add test suite documentation and update README
Copilot Nov 8, 2025
91afe5e
Add GitHub Actions workflow for automated testing
Copilot Nov 8, 2025
0e65f9d
Update GitHub Actions workflow to test on Python 3.13 and 3.14
Copilot Nov 8, 2025
14fc16c
Move source code to src/ subdirectory, run server from src/ working d…
Copilot Nov 8, 2025
71b4b50
fix move
Creeper19472 Nov 8, 2025
83dbb9d
fix file init
Creeper19472 Nov 8, 2025
d291b96
fix test.yml
Creeper19472 Nov 8, 2025
07f4941
Fix test working directory setup - ensure paths are relative to src/
Copilot Nov 8, 2025
c3fc715
add document_id in the response of create_document
Creeper19472 Nov 8, 2025
7c378bf
fix typo: `folder_id`
Creeper19472 Nov 8, 2025
44f01a7
add launch.json, add `upload_file_to_server()`
Creeper19472 Nov 8, 2025
b26fc2e
try to fix various issues
Creeper19472 Nov 8, 2025
6d7dcd5
fix uv
Creeper19472 Nov 8, 2025
a7e9728
fix encoding
Creeper19472 Nov 8, 2025
8adef55
fix assert 403 == 401
Creeper19472 Nov 8, 2025
9e4432c
Fix syntax error in conftest.py - parenthesize multiple exception types
Copilot Nov 8, 2025
2c00391
revert hello
Creeper19472 Nov 8, 2025
ac7b787
comment test_create_group_with_permissions
Creeper19472 Nov 8, 2025
850a3d4
update README
Creeper19472 Nov 8, 2025
75ca60f
remove certtools submodule
Creeper19472 Nov 8, 2025
67544b2
add certtools submodule
Creeper19472 Nov 8, 2025
a660411
update pyproject.toml
Creeper19472 Nov 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# GitHub Actions Workflows

## test.yml - Automated Testing

This workflow runs the pytest test suite automatically when:
- Code is pushed to any branch
- A pull request is opened or updated

### What it does:
1. Sets up a Python environment (tests on Python 3.13 and 3.14)
2. Installs project dependencies and test requirements
3. Creates necessary directories for the server
4. Runs the full test suite with pytest
5. Uploads test results and logs as artifacts (retained for 7 days)

### Configuration:
- **Timeout**: 10 minutes per test run
- **Matrix testing**: Tests across Python 3.13 and 3.14
- **Artifacts**: Test cache and server logs are uploaded for debugging

### Viewing Results:
- Check the "Actions" tab in the GitHub repository
- Test results will show pass/fail status for each Python version
- Download artifacts to review detailed logs if tests fail
52 changes: 52 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Run Tests

on:
push:
branches: [ "**" ]
pull_request:
branches: [ "**" ]

jobs:
test:
runs-on: ubuntu-latest

strategy:
matrix:
python-version: ["3.13", "3.14"]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Set up uv
run: |
python -m pip install --upgrade pip
pip install uv

- name: Install dependencies
run: |
uv sync --dev

- name: Create necessary directories
run: |
mkdir -p src/content/ssl
mkdir -p src/content/logs

- name: Run tests with pytest
run: |
uv run pytest tests/ -v --tb=short
timeout-minutes: 10

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-py${{ matrix.python-version }}
path: |
.pytest_cache/
src/content/logs/
retention-days: 7
9 changes: 5 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ app*.db
admin_password.txt
config.toml

content/ssl/
content/files/
content/logs/*
src/content/ssl/
src/content/files/
src/content/logs/*

.idea
.idea
uv.lock
4 changes: 2 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "certtools"]
path = certtools
[submodule "src/certtools"]
path = src/certtools
url = https://github.com/creeper19472/cfms_certtools
17 changes: 17 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [

{
"name": "Python 调试程序: 当前文件",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}/src"
}
]
}
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@ comments as the primary reference.

[doc-url]: https://cfms-server-doc.readthedocs.io/zh_CN/latest

## Testing

This repository includes an automated test suite built with pytest. To run the tests:

```bash
# Install dependencies
uv sync --dev

# Run all tests
uv run pytest

# Run specific test files
uv run pytest tests/test_basic.py
```

For more information about the test suite, see [tests/README.md](tests/README.md).

## Alpha Test

This is a project that is under active development and we are looking
Expand Down
6 changes: 0 additions & 6 deletions include/__init__.py

This file was deleted.

5 changes: 0 additions & 5 deletions include/database/models/__init__.py

This file was deleted.

6 changes: 0 additions & 6 deletions include/util/__init__.py

This file was deleted.

22 changes: 22 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[project]
name = "cfms-on-websocket"
version = "0.1.0"
description = "The server-side program for CFMS on WebSocket, a WebSocket-based implementation of the CFMS protocol."
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"cryptography>=46.0.3",
"filetype>=1.2.0",
"jsonschema>=4.25.1",
"pycryptodome>=3.23.0",
"pyjwt>=2.10.1",
"sqlalchemy>=2.0.44",
"tomlkit>=0.13.3",
"websockets>=15.0.1",
]

[dependency-groups]
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.21.0",
]
27 changes: 27 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[tool:pytest]
# Pytest configuration
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*

# Output settings
addopts =
-v
--tb=short
--strict-markers
--disable-warnings

# Markers
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
integration: marks tests as integration tests
unit: marks tests as unit tests

# Timeout for tests (in seconds)
timeout = 300

# Coverage settings (if pytest-cov is installed)
# --cov=include
# --cov-report=html
# --cov-report=term-missing
8 changes: 0 additions & 8 deletions requirements.txt

This file was deleted.

File renamed without changes.
Empty file added src/__init__.py
Empty file.
File renamed without changes.
File renamed without changes.
Empty file added src/include/__init__.py
Empty file.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,17 @@ class RequestListDirectoryHandler(RequestHandler):
"additionalProperties": False,
}

require_auth = True

def handle(self, handler: ConnectionHandler):

# Parse the directory listing request
folder_id: Optional[str] = handler.data.get("folder_id")

with Session() as session:
this_user = session.get(User, handler.username)
if not this_user or not this_user.is_token_valid(handler.token):
handler.conclude_request(
**{"code": 401, "message": "Invalid user or token", "data": {}}
)
return 401, folder_id
assert this_user is not None

if not folder_id:
parent = None
children = (
Expand Down Expand Up @@ -277,6 +276,8 @@ class RequestCreateDirectoryHandler(RequestHandler):
"required": ["name"],
}

require_auth = True

def handle(self, handler: ConnectionHandler):

# Parse the directory creation request
Expand All @@ -289,11 +290,8 @@ def handle(self, handler: ConnectionHandler):

with Session() as session:
this_user = session.get(User, handler.username)
if not this_user or not this_user.is_token_valid(handler.token):
handler.conclude_request(
**{"code": 403, "message": "Invalid user or token", "data": {}}
)
return 401, parent_id, handler.username
assert this_user is not None # require_auth ensures this

if parent_id:
parent = session.get(Folder, parent_id)
if not parent:
Expand Down
41 changes: 21 additions & 20 deletions include/handlers/document.py → src/include/handlers/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ def create_file_task(file: File, transfer_mode: int = 0):
if not file:
return None


now = time.time()
task = FileTask(
file_id=file.id,
Expand Down Expand Up @@ -84,6 +83,8 @@ class RequestGetDocumentInfoHandler(RequestHandler):
"required": ["document_id"],
}

require_auth = True

def handle(self, handler: ConnectionHandler):

document_id = handler.data.get("document_id")
Expand All @@ -92,23 +93,23 @@ def handle(self, handler: ConnectionHandler):
handler.conclude_request(400, {}, "Document ID is required")
return

if not handler.username:
handler.conclude_request(
**{"code": 403, "message": "Authentication is required", "data": {}}
)
return 401, document_id

with Session() as session:
user = session.get(User, handler.username)
document = session.get(Document, document_id)
assert user is not None

if user is None or not user.is_token_valid(handler.token):
handler.conclude_request(403, {}, "Invalid user or token")
return 401, document_id
document = session.get(Document, document_id)

if not document:
handler.conclude_request(404, {}, "Document not found")
return 404, document_id, handler.username

try:
document.get_latest_revision()
except NoActiveRevisionsError:
handler.conclude_request(
404, {}, "No active revisions found for this document"
)
return 404, document_id, handler.username

if not document.check_access_requirements(user, access_type="read"):
handler.conclude_request(403, {}, "Permission denied")
Expand Down Expand Up @@ -202,16 +203,15 @@ class RequestGetDocumentHandler(RequestHandler):
"additionalProperties": False,
}

require_auth = True

def handle(self, handler: ConnectionHandler):
document_id: str = handler.data["document_id"]

with Session() as session:
user = session.get(User, handler.username)
document = session.get(Document, document_id)

if user is None or not user.is_token_valid(handler.token):
handler.conclude_request(403, {}, "Invalid user or token")
return 401, document_id
assert user is not None

if not document:
handler.conclude_request(404, {}, "Document not found")
Expand Down Expand Up @@ -255,6 +255,8 @@ class RequestCreateDocumentHandler(RequestHandler):
"additionalProperties": False,
}

require_auth = True

def handle(self, handler: ConnectionHandler):

folder_id: str = handler.data.get("folder_id", "")
Expand All @@ -266,15 +268,12 @@ def handle(self, handler: ConnectionHandler):

with Session() as session:
user = session.get(User, handler.username)
assert user is not None

if not document_title:
handler.conclude_request(400, {}, "Document title is required")
return

if not user or not user.is_token_valid(handler.token):
handler.conclude_request(403, {}, "Invalid user or token")
return 401, folder_id

# 由于之后的逻辑可能提前结束,必须在实质性操作发生前鉴权
if not "create_document" in user.all_permissions:
handler.conclude_request(403, {}, "Permission denied")
Expand Down Expand Up @@ -363,7 +362,9 @@ def handle(self, handler: ConnectionHandler):
new_document_revision.file, transfer_mode=1
)
handler.conclude_request(
200, {"task_data": task_data}, "Task successfully created"
200,
{"document_id": new_document.id, "task_data": task_data},
"Task successfully created",
)
return 0, folder_id, {"title": document_title}, handler.username
else:
Expand Down
Loading