feat: complete Phase 3 scaffolding (templates, static, Docker, per-app skeletons)
- Per-app skeleton completion: property/client/setting now have services/,
tasks.py, views.py, urls.py, serializers.py, templates/<app>/, tests/
- admin.py added to all 10 apps (per spec §2.108 / §17.3)
- Top-level templates/: base.html, layouts/{app,auth}.html, components/
{topbar,sidebar,pagination,toast,modal,empty-state}.html, errors/
{403,404,500}.html
- static/: css/main.css (Tailwind entry), js/main.js (HTMX toast +
CSRF wiring per §7.4), vendor/.gitkeep
- tailwind.config.js: Primary teal + neutral slate + semantic colors,
Inter font stack, z-60/z-70, shadow-xs, slide-in-right animation per
UI_SYSTEM §2.7/§10.1
- package.json: tailwindcss-only build/watch
- Dockerfile + docker-compose.yml (6 services: web/db/redis/celery/
celery-beat/tailwind) + docker-compose.prod.yml + Makefile
- tests/ root: conftest.py with TenantClient fixture per §720,
integration/{property,client,release}/, e2e/, schemathesis skeleton
- Removed empty apps/tenant/models/ (tenant uses models.py per §17.1)
Validated: manage.py check passes; tree matches spec §2 exactly.
This commit is contained in:
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libpq-dev gcc \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements/base.txt requirements/base.txt
|
||||
RUN pip install --no-cache-dir -r requirements/base.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["uvicorn", "config.asgi:application", "--host", "0.0.0.0", "--port", "8000"]
|
||||
23
Makefile
Normal file
23
Makefile
Normal file
@@ -0,0 +1,23 @@
|
||||
.PHONY: dev migrate shell createsuperuser test lint tailwind-build
|
||||
|
||||
dev:
|
||||
docker compose up
|
||||
|
||||
migrate:
|
||||
docker compose exec web python manage.py migrate_schemas --shared
|
||||
docker compose exec web python manage.py migrate_schemas
|
||||
|
||||
shell:
|
||||
docker compose exec web python manage.py shell_plus
|
||||
|
||||
test:
|
||||
docker compose exec web pytest apps/ -v
|
||||
|
||||
lint:
|
||||
ruff check . && black --check .
|
||||
|
||||
tailwind-build:
|
||||
npm run build
|
||||
|
||||
createsuperuser:
|
||||
docker compose exec web python manage.py create_tenant_superuser
|
||||
0
apps/account/admin.py
Normal file
0
apps/account/admin.py
Normal file
0
apps/client/admin.py
Normal file
0
apps/client/admin.py
Normal file
0
apps/client/serializers.py
Normal file
0
apps/client/serializers.py
Normal file
0
apps/client/services/__init__.py
Normal file
0
apps/client/services/__init__.py
Normal file
0
apps/client/tasks.py
Normal file
0
apps/client/tasks.py
Normal file
0
apps/client/templates/client/.gitkeep
Normal file
0
apps/client/templates/client/.gitkeep
Normal file
0
apps/client/tests/__init__.py
Normal file
0
apps/client/tests/__init__.py
Normal file
5
apps/client/urls.py
Normal file
5
apps/client/urls.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.urls import path
|
||||
|
||||
app_name = "client"
|
||||
|
||||
urlpatterns: list = []
|
||||
0
apps/client/views.py
Normal file
0
apps/client/views.py
Normal file
0
apps/complex/admin.py
Normal file
0
apps/complex/admin.py
Normal file
0
apps/org/admin.py
Normal file
0
apps/org/admin.py
Normal file
0
apps/permission/admin.py
Normal file
0
apps/permission/admin.py
Normal file
0
apps/property/admin.py
Normal file
0
apps/property/admin.py
Normal file
0
apps/property/serializers.py
Normal file
0
apps/property/serializers.py
Normal file
0
apps/property/services/__init__.py
Normal file
0
apps/property/services/__init__.py
Normal file
0
apps/property/tasks.py
Normal file
0
apps/property/tasks.py
Normal file
0
apps/property/templates/property/.gitkeep
Normal file
0
apps/property/templates/property/.gitkeep
Normal file
0
apps/property/tests/__init__.py
Normal file
0
apps/property/tests/__init__.py
Normal file
5
apps/property/urls.py
Normal file
5
apps/property/urls.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.urls import path
|
||||
|
||||
app_name = "property"
|
||||
|
||||
urlpatterns: list = []
|
||||
0
apps/property/views.py
Normal file
0
apps/property/views.py
Normal file
0
apps/region/admin.py
Normal file
0
apps/region/admin.py
Normal file
0
apps/release/admin.py
Normal file
0
apps/release/admin.py
Normal file
0
apps/release/tests/__init__.py
Normal file
0
apps/release/tests/__init__.py
Normal file
0
apps/setting/admin.py
Normal file
0
apps/setting/admin.py
Normal file
0
apps/setting/serializers.py
Normal file
0
apps/setting/serializers.py
Normal file
0
apps/setting/services/__init__.py
Normal file
0
apps/setting/services/__init__.py
Normal file
0
apps/setting/tasks.py
Normal file
0
apps/setting/tasks.py
Normal file
0
apps/setting/templates/setting/.gitkeep
Normal file
0
apps/setting/templates/setting/.gitkeep
Normal file
0
apps/setting/tests/__init__.py
Normal file
0
apps/setting/tests/__init__.py
Normal file
5
apps/setting/urls.py
Normal file
5
apps/setting/urls.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.urls import path
|
||||
|
||||
app_name = "setting"
|
||||
|
||||
urlpatterns: list = []
|
||||
0
apps/setting/views.py
Normal file
0
apps/setting/views.py
Normal file
0
apps/tenant/admin.py
Normal file
0
apps/tenant/admin.py
Normal file
0
apps/tenant/tests/__init__.py
Normal file
0
apps/tenant/tests/__init__.py
Normal file
57
docker-compose.prod.yml
Normal file
57
docker-compose.prod.yml
Normal file
@@ -0,0 +1,57 @@
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
command: gunicorn config.asgi:application -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --workers 4
|
||||
env_file: .env
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
networks:
|
||||
- fonrey_net
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
env_file: .env
|
||||
environment:
|
||||
POSTGRES_DB: ${DB_NAME}
|
||||
POSTGRES_USER: ${DB_USER}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- fonrey_db_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- fonrey_net
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
volumes:
|
||||
- fonrey_redis_data:/data
|
||||
networks:
|
||||
- fonrey_net
|
||||
|
||||
celery:
|
||||
build: .
|
||||
command: celery -A config worker -l info --concurrency 4
|
||||
env_file: .env
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
networks:
|
||||
- fonrey_net
|
||||
|
||||
celery-beat:
|
||||
build: .
|
||||
command: celery -A config beat -l info
|
||||
env_file: .env
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
networks:
|
||||
- fonrey_net
|
||||
|
||||
volumes:
|
||||
fonrey_db_data:
|
||||
fonrey_redis_data:
|
||||
|
||||
networks:
|
||||
fonrey_net:
|
||||
driver: bridge
|
||||
78
docker-compose.yml
Normal file
78
docker-compose.yml
Normal file
@@ -0,0 +1,78 @@
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
command: uvicorn config.asgi:application --host 0.0.0.0 --port 8000 --reload
|
||||
ports:
|
||||
- "8000:8000"
|
||||
env_file: .env
|
||||
volumes:
|
||||
- .:/app
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
networks:
|
||||
- fonrey_net
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
ports:
|
||||
- "5432:5432"
|
||||
env_file: .env
|
||||
environment:
|
||||
POSTGRES_DB: ${DB_NAME}
|
||||
POSTGRES_USER: ${DB_USER}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- fonrey_db_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- fonrey_net
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- fonrey_redis_data:/data
|
||||
networks:
|
||||
- fonrey_net
|
||||
|
||||
celery:
|
||||
build: .
|
||||
command: celery -A config worker -l info
|
||||
env_file: .env
|
||||
volumes:
|
||||
- .:/app
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
networks:
|
||||
- fonrey_net
|
||||
|
||||
celery-beat:
|
||||
build: .
|
||||
command: celery -A config beat -l info
|
||||
env_file: .env
|
||||
volumes:
|
||||
- .:/app
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
networks:
|
||||
- fonrey_net
|
||||
|
||||
tailwind:
|
||||
image: node:20-alpine
|
||||
working_dir: /app
|
||||
command: sh -c "npm install && npm run watch"
|
||||
volumes:
|
||||
- .:/app
|
||||
networks:
|
||||
- fonrey_net
|
||||
|
||||
volumes:
|
||||
fonrey_db_data:
|
||||
fonrey_redis_data:
|
||||
|
||||
networks:
|
||||
fonrey_net:
|
||||
driver: bridge
|
||||
12
package.json
Normal file
12
package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "fonrey-frontend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "tailwindcss -i ./static/css/main.css -o ./static/css/output.css --minify",
|
||||
"watch": "tailwindcss -i ./static/css/main.css -o ./static/css/output.css --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tailwindcss": "^3.4.0"
|
||||
}
|
||||
}
|
||||
3
static/css/main.css
Normal file
3
static/css/main.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
32
static/js/main.js
Normal file
32
static/js/main.js
Normal file
@@ -0,0 +1,32 @@
|
||||
document.body.addEventListener("htmx:afterRequest", function (event) {
|
||||
var trigger = event.detail.xhr.getResponseHeader("HX-Trigger");
|
||||
if (!trigger) return;
|
||||
|
||||
try {
|
||||
var payload = JSON.parse(trigger);
|
||||
var toast = payload["fonrey:toast"];
|
||||
if (!toast) return;
|
||||
|
||||
var container = document.getElementById("toast-container");
|
||||
if (!container) return;
|
||||
|
||||
var node = document.createElement("div");
|
||||
node.className =
|
||||
"bg-white border border-neutral-200 rounded-lg shadow-xs px-4 py-3 min-w-[280px]";
|
||||
node.setAttribute("data-toast-type", toast.type || "info");
|
||||
node.textContent = toast.message || "";
|
||||
container.appendChild(node);
|
||||
|
||||
setTimeout(function () {
|
||||
node.remove();
|
||||
}, 4000);
|
||||
} catch (e) {
|
||||
}
|
||||
});
|
||||
|
||||
document.body.addEventListener("htmx:configRequest", function (event) {
|
||||
var meta = document.querySelector('meta[name="csrf-token"]');
|
||||
if (meta) {
|
||||
event.detail.headers["X-CSRFToken"] = meta.getAttribute("content");
|
||||
}
|
||||
});
|
||||
0
static/vendor/.gitkeep
vendored
Normal file
0
static/vendor/.gitkeep
vendored
Normal file
61
tailwind.config.js
Normal file
61
tailwind.config.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./templates/**/*.html",
|
||||
"./apps/**/templates/**/*.html",
|
||||
"./static/js/**/*.js",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
50: "#F0FDFA",
|
||||
100: "#CCFBF1",
|
||||
200: "#99F6E4",
|
||||
300: "#5EEAD4",
|
||||
400: "#2DD4BF",
|
||||
500: "#14B8A6",
|
||||
600: "#0F766E",
|
||||
700: "#115E59",
|
||||
800: "#134E4A",
|
||||
},
|
||||
neutral: {
|
||||
50: "#F8FAFC",
|
||||
100: "#F1F5F9",
|
||||
200: "#E2E8F0",
|
||||
300: "#CBD5E1",
|
||||
400: "#94A3B8",
|
||||
500: "#64748B",
|
||||
600: "#475569",
|
||||
700: "#334155",
|
||||
800: "#1E293B",
|
||||
900: "#0F172A",
|
||||
},
|
||||
success: { 600: "#16A34A" },
|
||||
warning: { 600: "#D97706" },
|
||||
danger: { 600: "#DC2626" },
|
||||
info: { 600: "#2563EB" },
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["Inter", "PingFang SC", "Microsoft YaHei", "sans-serif"],
|
||||
},
|
||||
zIndex: {
|
||||
60: "60",
|
||||
70: "70",
|
||||
},
|
||||
boxShadow: {
|
||||
xs: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
|
||||
},
|
||||
keyframes: {
|
||||
"slide-in-right": {
|
||||
"0%": { transform: "translateX(100%)", opacity: "0" },
|
||||
"100%": { transform: "translateX(0)", opacity: "1" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"slide-in-right": "slide-in-right 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
25
templates/base.html
Normal file
25
templates/base.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="csrf-token" content="{{ csrf_token }}">
|
||||
<title>{% block title %}Fonrey{% endblock %}</title>
|
||||
|
||||
<link rel="stylesheet" href="{% static 'css/output.css' %}">
|
||||
|
||||
{% block extra_head %}{% endblock %}
|
||||
|
||||
<script src="{% static 'vendor/htmx.min.js' %}" defer></script>
|
||||
<script src="{% static 'vendor/alpine.min.js' %}" defer></script>
|
||||
<script src="{% static 'js/main.js' %}" defer></script>
|
||||
</head>
|
||||
<body class="{% block body_class %}bg-neutral-50 text-neutral-900{% endblock %}">
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
<div id="toast-container" class="fixed bottom-4 right-4 z-70 flex flex-col gap-2"></div>
|
||||
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
3
templates/components/empty-state.html
Normal file
3
templates/components/empty-state.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<div class="flex flex-col items-center justify-center py-12 text-neutral-500">
|
||||
<p class="text-sm">{% block empty_message %}暂无数据{% endblock %}</p>
|
||||
</div>
|
||||
9
templates/components/modal.html
Normal file
9
templates/components/modal.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<div x-data="{ open: false }"
|
||||
x-show="open"
|
||||
x-cloak
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-neutral-900/40">
|
||||
<div class="bg-white rounded-lg shadow-xs max-w-lg w-full p-6"
|
||||
@click.outside="open = false">
|
||||
{% block modal_body %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
3
templates/components/pagination.html
Normal file
3
templates/components/pagination.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<nav class="flex items-center justify-between py-3" aria-label="分页">
|
||||
{% block pagination_body %}{% endblock %}
|
||||
</nav>
|
||||
6
templates/components/sidebar.html
Normal file
6
templates/components/sidebar.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<aside :class="sidebarOpen ? 'w-60' : 'w-16'"
|
||||
class="fixed top-14 left-0 bottom-0 z-20 bg-white border-r border-neutral-200 transition-all">
|
||||
<nav class="flex flex-col p-2 gap-1">
|
||||
{% block sidebar_items %}{% endblock %}
|
||||
</nav>
|
||||
</aside>
|
||||
4
templates/components/toast.html
Normal file
4
templates/components/toast.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<template id="toast-template">
|
||||
<div class="bg-white border border-neutral-200 rounded-lg shadow-xs px-4 py-3 min-w-[280px]"
|
||||
data-toast-type=""></div>
|
||||
</template>
|
||||
15
templates/components/topbar.html
Normal file
15
templates/components/topbar.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<header class="sticky top-0 z-20 bg-primary-800 text-white h-14 flex items-center px-4">
|
||||
<div class="w-[150px] flex items-center">
|
||||
<span class="font-semibold">Fonrey</span>
|
||||
</div>
|
||||
|
||||
<nav class="flex-1 flex items-center gap-1">
|
||||
{% block nav %}{% endblock %}
|
||||
</nav>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<button type="button" aria-label="通知"></button>
|
||||
<button type="button" aria-label="设置"></button>
|
||||
<button type="button" aria-label="账户"></button>
|
||||
</div>
|
||||
</header>
|
||||
10
templates/errors/403.html
Normal file
10
templates/errors/403.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}403 - 无权限{% endblock %}
|
||||
{% block content %}
|
||||
<div class="min-h-screen flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<h1 class="text-4xl font-semibold">403</h1>
|
||||
<p class="mt-2 text-neutral-600">您没有访问该资源的权限</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
10
templates/errors/404.html
Normal file
10
templates/errors/404.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}404 - 页面未找到{% endblock %}
|
||||
{% block content %}
|
||||
<div class="min-h-screen flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<h1 class="text-4xl font-semibold">404</h1>
|
||||
<p class="mt-2 text-neutral-600">页面未找到</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
10
templates/errors/500.html
Normal file
10
templates/errors/500.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}500 - 服务器错误{% endblock %}
|
||||
{% block content %}
|
||||
<div class="min-h-screen flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<h1 class="text-4xl font-semibold">500</h1>
|
||||
<p class="mt-2 text-neutral-600">服务器内部错误</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
38
templates/layouts/app.html
Normal file
38
templates/layouts/app.html
Normal file
@@ -0,0 +1,38 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block body_class %}bg-neutral-50 text-neutral-900{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div x-data="{ sidebarOpen: $persist(true) }" class="min-h-screen flex flex-col">
|
||||
{% include "components/topbar.html" %}
|
||||
|
||||
<div class="flex flex-1">
|
||||
{% include "components/sidebar.html" %}
|
||||
|
||||
<main :class="sidebarOpen ? 'ml-60' : 'ml-16'" class="flex-1 px-6 py-4 transition-all">
|
||||
{% block main %}{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="small-screen-gate"
|
||||
class="hidden fixed inset-0 bg-neutral-900/95 text-white items-center justify-center z-[100] text-center px-6">
|
||||
<p class="text-lg">Fonrey 当前仅支持桌面端(≥1280px),请在电脑上访问</p>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var gate = document.getElementById('small-screen-gate');
|
||||
function check() {
|
||||
if (window.innerWidth < 1280) {
|
||||
gate.classList.remove('hidden');
|
||||
gate.classList.add('flex');
|
||||
} else {
|
||||
gate.classList.add('hidden');
|
||||
gate.classList.remove('flex');
|
||||
}
|
||||
}
|
||||
window.addEventListener('resize', check);
|
||||
check();
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
9
templates/layouts/auth.html
Normal file
9
templates/layouts/auth.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block body_class %}bg-neutral-50 min-h-screen flex items-center justify-center{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-md w-full bg-white rounded-lg shadow-xs p-8">
|
||||
{% block auth_content %}{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
9
tests/conftest.py
Normal file
9
tests/conftest.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import pytest
|
||||
from django_tenants.test.client import TenantClient
|
||||
from django_tenants.utils import schema_context
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tenant_client(db, tenant):
|
||||
with schema_context(tenant.schema_name):
|
||||
yield TenantClient(tenant)
|
||||
0
tests/e2e/.gitkeep
Normal file
0
tests/e2e/.gitkeep
Normal file
0
tests/e2e/__init__.py
Normal file
0
tests/e2e/__init__.py
Normal file
0
tests/integration/__init__.py
Normal file
0
tests/integration/__init__.py
Normal file
0
tests/integration/client/.gitkeep
Normal file
0
tests/integration/client/.gitkeep
Normal file
0
tests/integration/client/__init__.py
Normal file
0
tests/integration/client/__init__.py
Normal file
0
tests/integration/property/.gitkeep
Normal file
0
tests/integration/property/.gitkeep
Normal file
0
tests/integration/property/__init__.py
Normal file
0
tests/integration/property/__init__.py
Normal file
0
tests/integration/release/__init__.py
Normal file
0
tests/integration/release/__init__.py
Normal file
6
tests/integration/release/test_client_update_api.py
Normal file
6
tests/integration/release/test_client_update_api.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="skeleton - implement after release endpoints are wired")
|
||||
def test_client_update_api_contract():
|
||||
pass
|
||||
Reference in New Issue
Block a user