WIP: add tests

This commit is contained in:
Valentin Gagarin 2025-03-31 15:13:35 +02:00
parent 1a81639e75
commit f3251ea89f
7 changed files with 28 additions and 65 deletions

View file

@ -13,6 +13,7 @@ let
in
{
options = {
enable = lib.mkEnableOption "Fediversity configuration";
domain = mkOption {
type =
with types;
@ -22,6 +23,7 @@ in
description = ''
Apex domain under which the services will be deployed.
'';
default = "fediversity.net";
};
pixelfed = {
enable = lib.mkEnableOption "Pixelfed";

View file

@ -13,7 +13,6 @@ let
exec ${pkgs.lib.getExe pkgs.python3} ${toString ./src/manage.py} $@
'';
jsonschema = pkgs.callPackage ./jsonschema.nix { } {
includeDefaults = false;
};
frontend-options = jsonschema.parseModule ../deployment/options.nix;
schema = with builtins; toFile "schema.json" (toJSON frontend-options);
@ -42,7 +41,7 @@ in
DATABASE_URL = "sqlite:///${toString ./src}/db.sqlite3";
};
shellHook = ''
cp -f ${pydantic} ${builtins.toString ./src/panel/configuration/schema.py}
install -m 644 ${pydantic} ${builtins.toString ./src/panel/configuration/schema.py}
ln -sf ${sources.htmx}/dist/htmx.js src/panel/static/htmx.min.js
# in production, secrets are passed via CREDENTIALS_DIRECTORY by systemd.

View file

@ -1,13 +1,13 @@
from django.db import models
from django.contrib.auth.models import User
from panel.configuration import forms
from panel.configuration import schema
from pydantic import BaseModel
def get_default_config():
return forms.Configuration().model_dump_json()
return schema.Model().model_dump_json()
class Configuration(models.Model):
@ -26,4 +26,4 @@ class Configuration(models.Model):
@property
def parsed_value(self) -> BaseModel:
return forms.Configuration.model_validate_json(self.value)
return schema.Model.model_validate_json(self.value)

View file

@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load rest_framework %}
{% block content %}
<form method="post" enctype="multipart/form-data" action="{% url 'save' %}">
<form method="post" enctype="multipart/form-data" action="{% url 'configuration_form' %}">
{% csrf_token %}
{% render_form serializer %}

View file

@ -13,7 +13,7 @@ class ConfigurationForm(TestCase):
password=self.password
)
self.config_url = reverse('save')
self.config_url = reverse('configuration_form')
def test_configuration_form_submission(self):
config = Configuration.objects.create(
@ -27,15 +27,13 @@ class ConfigurationForm(TestCase):
context = response.context[0]
# configuration should be disabled by default
self.assertFalse(context['view'].get_object().parsed_value.enable)
# ...and be displayed as such
self.assertFalse(context['form'].initial["enable"])
self.assertFalse(context['serializer'].instance.enable)
form_data = context['form'].initial.copy()
form_data.update(
enable=True,
mastodon_enable=True,
)
form_data = context['serializer'].instance.dict().copy()
form_data = {
"enable": True,
"mastodon.enable": True,
}
print(form_data)
response = self.client.post(self.config_url, data=form_data)
@ -46,4 +44,4 @@ class ConfigurationForm(TestCase):
self.assertTrue(config.parsed_value.enable)
self.assertTrue(config.parsed_value.mastodon.enable)
# this should not have changed
self.assertFalse(config.parsed_value.peertube.enable)
self.assertIsNone(config.parsed_value.peertube)

View file

@ -27,5 +27,4 @@ urlpatterns = [
path("services/", views.ServiceList.as_view(), name='service_list'),
path("configuration/", views.ConfigurationForm.as_view(), name='configuration_form'),
path("deployment/status/", views.DeploymentStatus.as_view(), name='deployment_status'),
path("save/", views.Save.as_view(), name='save'),
]

View file

@ -8,7 +8,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.views.generic import TemplateView, DetailView
from django.views.generic.edit import FormView
from django.shortcuts import render
from django.shortcuts import render, redirect
from rest_framework.renderers import TemplateHTMLRenderer
from rest_framework.response import Response
@ -48,59 +48,24 @@ class ConfigurationForm(LoginRequiredMixin, APIView):
def get(self, request):
config = self.get_object()
serializer = schema.Model.drf_serializer(instance=config.parsed_value)
return Response({
'serializer': schema.Model.drf_serializer(instance=config.parsed_value),
'serializer': serializer,
})
# TODO(@fricklerhandwerk):
# this should probably live somewhere else
def convert_enums_to_names(self, data_dict):
"""
Recursively convert all enum values in a dictionary to their string names.
This handles nested dictionaries and lists as well.
Needed for converting a Pydantic `BaseModel` instance to a `Form` input.
"""
if isinstance(data_dict, dict):
result = {}
for key, value in data_dict.items():
if isinstance(value, Enum):
# Convert Enum to its name
result[key] = value.name
elif isinstance(value, (dict, list)):
# Recursively process nested structures
result[key] = self.convert_enums_to_names(value)
else:
# Keep other values as is
result[key] = value
return result
elif isinstance(data_dict, list):
# Process each item in the list
return [self.convert_enums_to_names(item) for item in data_dict]
elif isinstance(data_dict, Enum):
# Convert single Enum value
return data_dict.name
else:
# Return non-dict, non-list, non-Enum values as is
return data_dict
def get_initial(self):
initial = super().get_initial()
def post(self, request):
config = self.get_object()
config_dict = config.parsed_value.model_dump()
serializer = schema.Model.drf_serializer(
instance=config.parsed_value,
data=request.data
)
initial.update(self.convert_enums_to_names(config_dict))
return initial
class Save(ConfigurationForm):
def form_valid(self, form):
obj = self.get_object()
obj.value = form.to_python().model_dump_json()
obj.save()
return super().form_valid(form)
if not serializer.is_valid():
return Response({'serializer': serializer})
config.value = json.dumps(serializer.validated_data)
config.save()
return redirect(self.success_url)
class DeploymentStatus(ConfigurationForm):
def form_valid(self, form):