diff --git a/starpunk/micropub.py b/starpunk/micropub.py index 2bb05c0..0e2a4e8 100644 --- a/starpunk/micropub.py +++ b/starpunk/micropub.py @@ -287,6 +287,17 @@ def handle_create(data: dict, token_info: dict): "insufficient_scope", "Token lacks create scope", status_code=403 ) + # Extract mp-slug BEFORE normalizing properties (it's not a property!) + # mp-slug is a Micropub server extension parameter that gets filtered during normalization + custom_slug = None + if isinstance(data, dict) and 'mp-slug' in data: + # Handle both form-encoded (list) and JSON (could be string or list) + slug_value = data.get('mp-slug') + if isinstance(slug_value, list) and slug_value: + custom_slug = slug_value[0] + elif isinstance(slug_value, str): + custom_slug = slug_value + # Normalize and extract properties try: properties = normalize_properties(data) @@ -295,14 +306,6 @@ def handle_create(data: dict, token_info: dict): tags = extract_tags(properties) published_date = extract_published_date(properties) - # Extract custom slug if provided (Micropub extension) - custom_slug = None - if 'mp-slug' in properties: - # mp-slug is an array in Micropub format - slug_values = properties.get('mp-slug', []) - if slug_values and len(slug_values) > 0: - custom_slug = slug_values[0] - except MicropubValidationError as e: raise e except Exception as e: diff --git a/tests/test_micropub.py b/tests/test_micropub.py index 45f712b..7cf482c 100644 --- a/tests/test_micropub.py +++ b/tests/test_micropub.py @@ -188,6 +188,64 @@ def test_micropub_create_with_categories(client, app, mock_valid_token): assert 'Location' in response.headers +def test_micropub_create_with_custom_slug_form(client, app, mock_valid_token): + """Test creating a note with custom slug via form-encoded request""" + with patch('starpunk.routes.micropub.verify_external_token', mock_valid_token): + response = client.post( + '/micropub', + data={ + 'h': 'entry', + 'content': 'This is a test for custom slugs', + 'mp-slug': 'my-custom-slug' + }, + headers={'Authorization': 'Bearer valid_token'} + ) + + assert response.status_code == 201 + assert 'Location' in response.headers + + # Verify the custom slug was used + location = response.headers['Location'] + assert location.endswith('/notes/my-custom-slug') + + # Verify note exists with the custom slug + with app.app_context(): + note = get_note('my-custom-slug') + assert note is not None + assert note.slug == 'my-custom-slug' + assert note.content == 'This is a test for custom slugs' + + +def test_micropub_create_with_custom_slug_json(client, app, mock_valid_token): + """Test creating a note with custom slug via JSON request""" + with patch('starpunk.routes.micropub.verify_external_token', mock_valid_token): + response = client.post( + '/micropub', + json={ + 'type': ['h-entry'], + 'properties': { + 'content': ['JSON test with custom slug'] + }, + 'mp-slug': 'json-custom-slug' + }, + headers={'Authorization': 'Bearer valid_token'} + ) + + assert response.status_code == 201 + assert 'Location' in response.headers + + # Verify the custom slug was used + location = response.headers['Location'] + assert location.endswith('/notes/json-custom-slug') + + # Verify note exists with the custom slug + with app.app_context(): + note = get_note('json-custom-slug') + assert note is not None + assert note.slug == 'json-custom-slug' + assert note.content == 'JSON test with custom slug' + + # Query Tests