Finished with Phase 2.6

This commit is contained in:
JakeBreath
2026-08-04 01:46:58 -05:00
parent 8c13c5e5ec
commit b7b21ab5e0
11 changed files with 1350 additions and 41 deletions
+254
View File
@@ -4,6 +4,7 @@ import os
import secrets
import shutil
import tempfile
import unittest
import zipfile
from django.contrib.auth import get_user_model
@@ -1320,3 +1321,256 @@ class UGCAnimationTests(UGCMediaTestCase, UGCGatedTestCase):
self.client.login(username='Alice', password='pw')
resp = self.client.get(reverse('library:api_packs_latest', args=['jakebreath', 'testpack']))
self.assertEqual(resp.status_code, 404)
def _geo_model_zip():
geo = {
'format_version': '1.12.0',
'afw_bone_textures': {'body': 'needsofnature:textures/entity/zombie/zombie.png'},
'minecraft:geometry': [{
'description': {'identifier': 'geometry.unknown', 'texture_width': 64, 'texture_height': 64},
'bones': [
{'name': 'root', 'pivot': [0, 0, 0]},
{'name': 'body', 'pivot': [0, 12, 0], 'cubes': [
{'origin': [-4, 0, -4], 'size': [8, 8, 8], 'uv': [0, 0]},
]},
],
}],
}
return _zip_bytes({
'pack.mcmeta': json.dumps({
'pack': {'pack_format': 88},
'animationframework': {'id': 'christmaspuffin:models_and_textures', 'name': 'Models',
'author': 'CP', 'version': '1.0'},
}),
'assets/animationframework/geckolib/models/entity/zombie.m.geo.json': json.dumps(geo),
'assets/needsofnature/textures/entity/zombie/zombie.png': b'PNG-DATA',
})
def _logical_zip():
return _zip_bytes({
'pack.mcmeta': json.dumps({'pack': {'pack_format': 61}}),
'assets/usefulcum/items/potion_gender.json': json.dumps(
{'model': {'type': 'minecraft:model', 'model': 'usefulcum:item/potion_gender'}}),
'assets/usefulcum/textures/item/potion_gender.png': b'PNG',
'data/usefulcum/recipe/cum_combination.json': json.dumps({
'type': 'minecraft:crafting_shapeless',
'ingredients': ['minecraft:potion', 'minecraft:potion'],
'result': {'id': 'minecraft:glass_bottle', 'count': 2},
}),
'data/usefulcum/advancement/recipe/blaze_cum.json': json.dumps(
{'rewards': {'function': 'usefulcum:recipe/blaze_cum'}}),
'data/usefulcum/advancement/change_gender.json': json.dumps(
{'rewards': {'function': 'usefulcum:change_gender'}}),
'data/usefulcum/function/change_gender.mcfunction': b'# x',
'data/usefulcum/function/tick.mcfunction': b'# y',
})
class UGCContentTabsTests(UGCMediaTestCase, UGCGatedTestCase):
def setUp(self):
User = get_user_model()
self.alice = User.objects.create_user(username='Alice', password='pw')
UserProfile.objects.get_or_create(user=self.alice)
def _write_zip(self, data):
with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as f:
f.write(data)
return f.name
def _upload_version(self, slug, name, content):
self.gate()
self.client.login(username='Alice', password='pw')
resp = self.client.post(
reverse('library:api_upload_temp'),
{'kind': 'version', 'file': SimpleUploadedFile(name, content, content_type='application/zip')},
HTTP_X_REQUESTED_WITH='XMLHttpRequest',
)
self.assertEqual(resp.status_code, 200)
return self.client.post(
reverse('library:version_upload', args=[slug]), {'version_name': '1.0.0'},
)
def test_read_pack_meta_without_animdefs(self):
from library.zips import read_pack_meta
path = self._write_zip(_geo_model_zip())
try:
meta = read_pack_meta(path)
finally:
os.unlink(path)
self.assertEqual(meta['animation_id'], 'christmaspuffin:models_and_textures')
self.assertEqual(meta['authors'], ['CP'])
def test_read_models_manifest(self):
from library.zips import read_models_manifest
path = self._write_zip(_geo_model_zip())
try:
manifest = read_models_manifest(path)
finally:
os.unlink(path)
self.assertIsNotNone(manifest)
model = manifest['models'][0]
self.assertEqual(model['name'], 'zombie.m.geo.json')
self.assertEqual(model['bones'], 2)
self.assertEqual(model['cubes'], 1)
self.assertEqual(
model['bone_textures']['body'],
'assets/needsofnature/textures/entity/zombie/zombie.png',
)
def test_read_logical_manifest(self):
from library.zips import read_logical_manifest
path = self._write_zip(_logical_zip())
try:
manifest = read_logical_manifest(path)
finally:
os.unlink(path)
self.assertIsNotNone(manifest)
self.assertEqual(manifest['items'][0]['name'], 'usefulcum:potion_gender')
self.assertEqual(manifest['recipes'][0]['ingredients'], ['minecraft:potion'])
self.assertEqual(manifest['recipes'][0]['result'], 'minecraft:glass_bottle x2')
self.assertEqual(manifest['custom_recipes'][0]['name'], 'blaze_cum')
self.assertEqual(manifest['advancements'][0]['name'], 'change_gender')
self.assertEqual(len(manifest['functions']), 2)
def test_models_pack_gets_id_and_models_tab(self):
self.project = Project.objects.create(
slug='models-pack', title='Models Pack', category='non_pack', owner=self.alice,
)
self._upload_version('models-pack', 'pack.zip', _geo_model_zip())
version = self.project.versions.first()
self.assertEqual(version.animation_id, 'christmaspuffin:models_and_textures')
self.assertTrue(version.models_manifest)
self.assertFalse(version.animation_manifest)
self.gate()
resp = self.client.get(reverse('library:project_detail', args=['models-pack']))
self.assertContains(resp, 'tab-models')
self.assertContains(resp, 'Render')
self.assertNotContains(resp, 'tab-animations')
def test_logical_pack_shows_only_logical_tab(self):
self.project = Project.objects.create(
slug='logical-pack', title='Logical Pack', category='non_pack', owner=self.alice,
)
self._upload_version('logical-pack', 'pack.zip', _logical_zip())
version = self.project.versions.first()
self.assertTrue(version.logical_manifest)
self.assertFalse(version.models_manifest)
self.assertFalse(version.animation_manifest)
self.gate()
resp = self.client.get(reverse('library:project_detail', args=['logical-pack']))
self.assertContains(resp, 'tab-logical')
self.assertNotContains(resp, 'tab-models')
self.assertNotContains(resp, 'tab-animations')
self.assertContains(resp, 'usefulcum:potion_gender')
def test_pack_asset_serves_member(self):
self.project = Project.objects.create(
slug='models-pack', title='Models Pack', category='non_pack', owner=self.alice,
)
self._upload_version('models-pack', 'pack.zip', _geo_model_zip())
version = self.project.versions.first()
self.gate()
resp = self.client.get(reverse(
'library:pack_asset',
args=['models-pack', version.pk, 'assets/needsofnature/textures/entity/zombie/zombie.png'],
))
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp['Content-Type'].split(';')[0], 'image/png')
self.assertEqual(resp.content, b'PNG-DATA')
def test_pack_asset_rejects_unknown_member(self):
self.project = Project.objects.create(
slug='models-pack', title='Models Pack', category='non_pack', owner=self.alice,
)
self._upload_version('models-pack', 'pack.zip', _geo_model_zip())
version = self.project.versions.first()
self.gate()
resp = self.client.get(reverse(
'library:pack_asset',
args=['models-pack', version.pk, 'assets/nope/nothing.png'],
))
self.assertEqual(resp.status_code, 404)
def test_gallery_video_is_muted(self):
from library.storage import store_file
self.project = Project.objects.create(
slug='muted', title='Muted', category='mod', owner=self.alice,
)
index = store_file(
self.alice, 'asset',
SimpleUploadedFile('clip.webm', b'webm', content_type='video/webm'),
self.project.pk, subdir='gallery',
)
ProjectAsset.objects.create(project=self.project, file=index, uploaded_by=self.alice)
self.gate()
resp = self.client.get(reverse('library:project_detail', args=['muted']))
self.assertContains(resp, 'muted')
def make_gif_bytes():
import PIL.Image
frames = [PIL.Image.new('RGB', (8, 8), c) for c in ('red', 'green')]
buf = io.BytesIO()
frames[0].save(buf, format='GIF', save_all=True, append_images=frames[1:], duration=100, loop=0)
return buf.getvalue()
class UGCThumbnailTests(UGCMediaTestCase, UGCGatedTestCase):
@unittest.skipUnless(shutil.which('ffmpeg'), 'ffmpeg required')
def test_video_and_gif_thumbnails(self):
User = get_user_model()
alice = User.objects.create_user(username='Alice', password='pw')
UserProfile.objects.get_or_create(user=alice)
project = Project.objects.create(slug='media', title='Media', category='mod', owner=alice)
from library.storage import store_file
gif = ProjectAsset.objects.create(
project=project, uploaded_by=alice,
file=store_file(
alice, 'asset',
SimpleUploadedFile('anim.gif', make_gif_bytes(), content_type='image/gif'),
project.pk, subdir='gallery',
),
)
png = ProjectAsset.objects.create(
project=project, uploaded_by=alice,
file=store_file(
alice, 'asset',
SimpleUploadedFile('pic.png', make_png_bytes(), content_type='image/png'),
project.pk, subdir='gallery',
),
)
self.gate()
# Gif gets a cached JPEG thumbnail.
resp = self.client.get(reverse('library:asset_thumbnail', args=['media', gif.pk]))
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp['Content-Type'].split(';')[0], 'image/jpeg')
self.assertGreater(len(b''.join(resp.streaming_content)), 0)
# Cached on the second request.
resp2 = self.client.get(reverse('library:asset_thumbnail', args=['media', gif.pk]))
self.assertEqual(resp2.status_code, 200)
# A plain image is not thumbnailed → 404.
resp3 = self.client.get(reverse('library:asset_thumbnail', args=['media', png.pk]))
self.assertEqual(resp3.status_code, 404)
def test_gallery_grid_uses_thumbnail_for_gif(self):
User = get_user_model()
alice = User.objects.create_user(username='Alice', password='pw')
UserProfile.objects.get_or_create(user=alice)
project = Project.objects.create(slug='media', title='Media', category='mod', owner=alice)
from library.storage import store_file
ProjectAsset.objects.create(
project=project, uploaded_by=alice,
file=store_file(
alice, 'asset',
SimpleUploadedFile('anim.gif', make_gif_bytes(), content_type='image/gif'),
project.pk, subdir='gallery',
),
)
self.gate()
resp = self.client.get(reverse('library:project_detail', args=['media']))
self.assertContains(resp, '/thumb/')