diff --git a/.gitattributes b/.gitattributes index 6a5ef04..bad39d7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,3 +6,8 @@ *.json text eol=lf *.md text eol=lf *.bat text eol=crlf +# Test fixtures are byte-exact (hashes, signatures): never convert line endings. +tests/fixtures/** -text +*.apk binary +*.jar binary +*.obb binary diff --git a/README.md b/README.md index 8518264..fb40d32 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,8 @@ See what the headset sees, install games and Android apps, move files and text a
Offline fixture & music.
\nOne\t line.
" + }, + "license": "MIT", + "authorName": "Example Developer", + "icon": { + "de": { + "name": "/org.example.app/de/icon.png" + }, + "en-US": { + "name": "/org.example.app/en-US/icon.png" + } + }, + "featureGraphic": { + "fr": { + "name": "/org.example.app/fr/featureGraphic.png" + } + }, + "screenshots": { + "phone": { + "de": [ + { + "name": "/org.example.app/de/phoneScreenshots/1.png" + } + ], + "en-US": [ + { + "name": "/org.example.app/en-US/phoneScreenshots/1.png" + }, + { + "name": "/org.example.app/en-US/phoneScreenshots/2.png" + }, + { + "name": "/org.example.app/en-US/phoneScreenshots/3.png" + }, + { + "name": "/org.example.app/en-US/phoneScreenshots/4.png" + } + ] + }, + "sevenInch": { + "fr": [ + { + "name": "/org.example.app/fr/sevenInchScreenshots/1.png" + }, + { + "name": "/org.example.app/fr/sevenInchScreenshots/2.png" + }, + { + "name": "/org.example.app/fr/sevenInchScreenshots/3.png" + }, + { + "name": "/org.example.app/fr/sevenInchScreenshots/4.png" + } + ] + } + } + }, + "versions": { + "1": { + "manifest": { + "versionName": "1", + "versionCode": 1, + "usesSdk": { + "minSdkVersion": 21 + }, + "nativecode": [] + }, + "file": { + "name": "/example1.apk", + "sha256": "3e1e2658aef79aaf21aeb8d6705dbc5b251e51627bfdfc53650da50ed2c38c79", + "size": 51 + }, + "added": 1700000000000 + }, + "2": { + "manifest": { + "versionName": "2", + "versionCode": 2, + "usesSdk": { + "minSdkVersion": 30 + }, + "nativecode": [ + "arm64-v8a" + ] + }, + "file": { + "name": "/example2.apk", + "sha256": "3e1e2658aef79aaf21aeb8d6705dbc5b251e51627bfdfc53650da50ed2c38c79", + "size": 51 + }, + "added": 1700000000000 + }, + "3": { + "manifest": { + "versionName": "3", + "versionCode": 3, + "usesSdk": { + "minSdkVersion": 31 + }, + "nativecode": [] + }, + "file": { + "name": "/example3.apk", + "sha256": "3e1e2658aef79aaf21aeb8d6705dbc5b251e51627bfdfc53650da50ed2c38c79", + "size": 51 + }, + "added": 1700000000000 + }, + "4": { + "manifest": { + "versionName": "4", + "versionCode": 4, + "usesSdk": { + "minSdkVersion": 21 + }, + "nativecode": [ + "x86_64" + ] + }, + "file": { + "name": "/example4.apk", + "sha256": "3e1e2658aef79aaf21aeb8d6705dbc5b251e51627bfdfc53650da50ed2c38c79", + "size": 51 + }, + "added": 1700000000000 + } + } + } + } +} diff --git a/tests/fixtures/fdroid/entry.jar b/tests/fixtures/fdroid/entry.jar new file mode 100644 index 0000000..b73864a Binary files /dev/null and b/tests/fixtures/fdroid/entry.jar differ diff --git a/tests/fixtures/fdroid/example.apk b/tests/fixtures/fdroid/example.apk new file mode 100644 index 0000000..e53f5b7 --- /dev/null +++ b/tests/fixtures/fdroid/example.apk @@ -0,0 +1 @@ +Fixture APK payload, deliberately not installable. diff --git a/tests/fixtures/fdroid/fingerprint.txt b/tests/fixtures/fdroid/fingerprint.txt new file mode 100644 index 0000000..494e9b0 --- /dev/null +++ b/tests/fixtures/fdroid/fingerprint.txt @@ -0,0 +1 @@ +0e87b227cd414d7093fb150fda81f1754900f3abc810e6785d8e0767c6eb798a diff --git a/tests/fixtures/fdroid/index-v1.jar b/tests/fixtures/fdroid/index-v1.jar new file mode 100644 index 0000000..6ea58d2 Binary files /dev/null and b/tests/fixtures/fdroid/index-v1.jar differ diff --git a/tests/fixtures/fdroid/index-v2.json b/tests/fixtures/fdroid/index-v2.json new file mode 100644 index 0000000..6d443c6 --- /dev/null +++ b/tests/fixtures/fdroid/index-v2.json @@ -0,0 +1 @@ +{"repo": {"name": {"en-US": "Fixture"}}, "packages": {"org.example.app": {"metadata": {"name": {"en-US": "Example"}, "summary": {"en-US": "Offline fixture"}, "license": "MIT"}, "versions": {"1": {"manifest": {"versionName": "1", "versionCode": 1, "usesSdk": {"minSdkVersion": 21}, "nativecode": []}, "file": {"name": "/example1.apk", "sha256": "3e1e2658aef79aaf21aeb8d6705dbc5b251e51627bfdfc53650da50ed2c38c79", "size": 51}, "added": 1700000000000}, "2": {"manifest": {"versionName": "2", "versionCode": 2, "usesSdk": {"minSdkVersion": 30}, "nativecode": ["arm64-v8a"]}, "file": {"name": "/example2.apk", "sha256": "3e1e2658aef79aaf21aeb8d6705dbc5b251e51627bfdfc53650da50ed2c38c79", "size": 51}, "added": 1700000000000}, "3": {"manifest": {"versionName": "3", "versionCode": 3, "usesSdk": {"minSdkVersion": 31}, "nativecode": []}, "file": {"name": "/example3.apk", "sha256": "3e1e2658aef79aaf21aeb8d6705dbc5b251e51627bfdfc53650da50ed2c38c79", "size": 51}, "added": 1700000000000}, "4": {"manifest": {"versionName": "4", "versionCode": 4, "usesSdk": {"minSdkVersion": 21}, "nativecode": ["x86_64"]}, "file": {"name": "/example4.apk", "sha256": "3e1e2658aef79aaf21aeb8d6705dbc5b251e51627bfdfc53650da50ed2c38c79", "size": 51}, "added": 1700000000000}}}}} \ No newline at end of file diff --git a/tests/fixtures/fdroid/izzy-entry.jar b/tests/fixtures/fdroid/izzy-entry.jar new file mode 100644 index 0000000..c518648 Binary files /dev/null and b/tests/fixtures/fdroid/izzy-entry.jar differ diff --git a/tests/fixtures/library/README.md b/tests/fixtures/library/README.md new file mode 100644 index 0000000..04d4f03 --- /dev/null +++ b/tests/fixtures/library/README.md @@ -0,0 +1,17 @@ +# Library fixtures + +`icon.png` is a synthetic 2×2 RGBA fixture with opaque, half-transparent and +transparent pixels. `steam-responses.json` includes the Open Saber Plus +shortcut ID/name and home path read from the Frame's existing metadata on +2026-09-28; the `configure` response is synthetic, matching our helper's +contract. Tests never contact the network. + +The existing fakeframe CEF shim models artwork and collection methods from +SteamTracking's `ClientExtracted/steamui/chunk~2dcc5aaf7.js` and +SteamDeckHomebrew/decky-frontend-lib's `src/globals/steam-client/App.ts`, read +2026-09-28. These methods were not captured from this headset: Steam's client +was unavailable. The Node-based test runs the actual generated JavaScript +against that fixture; it is skipped when Node is absent. + +`icon.jpg` is the same synthetic icon converted with macOS `sips` to exercise +JPEG SOF parsing. `sips` is not used by the product or tests. diff --git a/tests/fixtures/library/check-renderer.js b/tests/fixtures/library/check-renderer.js new file mode 100644 index 0000000..1e2e06f --- /dev/null +++ b/tests/fixtures/library/check-renderer.js @@ -0,0 +1,21 @@ +const fs=require('fs'),vm=require('vm'),assert=require('assert'); +const stub=require(process.cwd()+'/tests/fakeframe/rootfs/usr/local/lib/fakeframe/canvas_stub'); +(async()=>{ + const surface=stub.surface(),ctx=vm.createContext(surface); + vm.runInContext(fs.readFileSync('frame/android/library_artwork.js','utf8'),ctx); + const result=await ctx.renderLibraryArtwork({label:'Example Game',images:{icon:['png','fixture']}}); + assert.deepEqual(Object.keys(result.images),['grid','wide','hero','logo','icon']); + for(const [slot,size] of Object.entries({grid:[600,900],wide:[920,430],hero:[3840,1240],logo:[1280,480],icon:[256,256]})) { + assert.equal(result.images[slot][0],'png'); + const b=Buffer.from(result.images[slot][1],'base64');assert.equal(b.readUInt32BE(16),size[0]);assert.equal(b.readUInt32BE(20),size[1]); + } + // A photo scene is JPEG (Steam's 12 MiB limit at hero size); the logo stays transparent PNG. + const photo=await ctx.renderLibraryArtwork({label:'Photo',images:{hero:['jpg','fixture'],banner:['jpg','fixture']}}); + for(const slot of ['wide','hero'])assert.equal(photo.images[slot][0],'jpg'); + for(const slot of ['grid','logo','icon'])assert.equal(photo.images[slot][0],'png'); + const hero=surface.canvases.find(c=>c.width===3840);assert.equal(hero.text.length,0); + const logo=surface.canvases.find(c=>c.width===1280);assert(logo.text.length);assert.equal(logo.draws,0); + const before=surface.canvases.length;await ctx.renderLibraryArtwork({label:'No Icon',images:{}}); + assert.equal(surface.canvases.slice(before).find(c=>c.width===3840).text.length,0); + console.log('five dimensions, textless hero, title logo: OK'); +})().catch(e=>{console.error(e);process.exit(1)}); diff --git a/tests/fixtures/library/icon.jpg b/tests/fixtures/library/icon.jpg new file mode 100644 index 0000000..ea4d67c Binary files /dev/null and b/tests/fixtures/library/icon.jpg differ diff --git a/tests/fixtures/library/icon.png b/tests/fixtures/library/icon.png new file mode 100644 index 0000000..2e2af85 Binary files /dev/null and b/tests/fixtures/library/icon.png differ diff --git a/tests/fixtures/library/steam-responses.json b/tests/fixtures/library/steam-responses.json new file mode 100644 index 0000000..8b2c2c7 --- /dev/null +++ b/tests/fixtures/library/steam-responses.json @@ -0,0 +1,12 @@ +{ + "home": "/home/steamos", + "shortcuts": [ + { + "appid": 3346865537, + "name": "Open Saber Plus" + } + ], + "configure": { + "warnings": [] + } +} diff --git a/tests/fixtures/more_sources/README.md b/tests/fixtures/more_sources/README.md new file mode 100644 index 0000000..8f33165 --- /dev/null +++ b/tests/fixtures/more_sources/README.md @@ -0,0 +1,23 @@ +Recorded 2026-09-28 from public publisher endpoints using FrameControl/0.1 or +`gh api`. JSON fixtures are reduced to fields consumed by the adapters; API +values are unchanged. No token, cookies or signed download URL is included. + +- `*-releases.json`: `/repos/{repo}/releases?per_page=10`, first two releases, + for KhronosGroup/OpenXR-SDK-Source, icosa-foundation/open-brush and + SgtBilko76/SuperTux-3D (one release). +- `topic.json`: `/search/repositories?q=topic:openxr+archived:false&sort=stars&per_page=3`. +- `itch-feed.txt`: `https://itch.io/games/free/platform-android/tag-openxr.xml`. +- `itch-robots.txt`: `https://itch.io/robots.txt`. +- `itch-author-robots.txt`: `https://godotvr.itch.io/robots.txt`. + +The synthetic ZIP in tests is only a transport/integrity fixture, not an +installable APK. Actual APK parsing was verified separately on the downloaded +Khronos Vulkan sample; see docs/apk-sources.md. + +Artwork follow-up: `topic.json` now retains `owner.avatar_url` from authenticated +repository API reads. `artwork-check.json` records HTTPS response status, +Content-Type and image magic checks for all curated URLs and three topic +results. All curated URLs returned real images; LWJGL's social preview returned +HTTP 429. This fixture is evidence of a point-in-time check, not an uptime test. +Open Brush screenshots came from the Steam appdetails response for app 1634870, +linked by its README. SuperTux's README links the recorded upstream screenshot. diff --git a/tests/fixtures/more_sources/artwork-check.json b/tests/fixtures/more_sources/artwork-check.json new file mode 100644 index 0000000..d16be22 --- /dev/null +++ b/tests/fixtures/more_sources/artwork-check.json @@ -0,0 +1,120 @@ +[ + { + "url": "https://avatars.githubusercontent.com/u/2757344?v=4", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://avatars.githubusercontent.com/u/784805?v=4", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://avatars.githubusercontent.com/u/94376830?v=4", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://opengraph.githubassets.com/1/KhronosGroup/OpenXR-SDK-Source", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://opengraph.githubassets.com/1/LWJGL/lwjgl3", + "error": "HTTP Error 429: Too Many Requests" + }, + { + "url": "https://opengraph.githubassets.com/1/bjornbytes/lovr", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://opengraph.githubassets.com/1/sahibzada-allahyar/YC-Killer", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://raw.githubusercontent.com/KhronosGroup/OpenXR-SDK-Source/3ed64d0f9bb680f24b80a085091e5c8fab38f7b7/src/tests/hello_xr/android_resources/vulkan/mipmap-xxxhdpi/ic_helloxr_launcher.png", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://raw.githubusercontent.com/SgtBilko76/SuperTux-3D/1955493ee6f1000e048c58db40d4904df827210e/data/images/engine/icons/supertux-256x256.png", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://raw.githubusercontent.com/icosa-foundation/open-brush/56acbce831c7e9f257bfee9e21853da99773787b/Assets/Resources/DefaultImages/OpenBrushLogo.png", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://raw.githubusercontent.com/icosa-foundation/open-brush/56acbce831c7e9f257bfee9e21853da99773787b/open-brush.png", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1634870/ss_0a9c208e26a43cf34879c2ca361d4c8f18af8cba.1920x1080.jpg", + "status": 200, + "content_type": "image/jpeg", + "image": true + }, + { + "url": "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1634870/ss_19b25b86ef55c0d8769a65135d60eaae8fa40553.1920x1080.jpg", + "status": 200, + "content_type": "image/jpeg", + "image": true + }, + { + "url": "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1634870/ss_785ea37d63378146dfe0f0ffa3f1d5c155ca978f.1920x1080.jpg", + "status": 200, + "content_type": "image/jpeg", + "image": true + }, + { + "url": "https://www.supertux.org/images/0_7_0/github_preview.png", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://raw.githubusercontent.com/arpruss/OpenSaberPlus/8c5295cdaadf02ea7418b0c6cbea20240ba913af/icon.png", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://opengraph.githubassets.com/1/arpruss/OpenSaberPlus", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://raw.githubusercontent.com/arpruss/OpenSaberPlus/8c5295cdaadf02ea7418b0c6cbea20240ba913af/doc/images/OS0.4.0_1.gif", + "status": 200, + "content_type": "image/gif", + "image": true + }, + { + "url": "https://raw.githubusercontent.com/arpruss/OpenSaberPlus/8c5295cdaadf02ea7418b0c6cbea20240ba913af/doc/images/OS0.4.0_2.gif", + "status": 200, + "content_type": "image/gif", + "image": true + }, + { + "url": "https://raw.githubusercontent.com/arpruss/OpenSaberPlus/8c5295cdaadf02ea7418b0c6cbea20240ba913af/doc/images/OS0.4.0_3.gif", + "status": 200, + "content_type": "image/gif", + "image": true + } +] \ No newline at end of file diff --git a/tests/fixtures/more_sources/brush-releases.json b/tests/fixtures/more_sources/brush-releases.json new file mode 100644 index 0000000..c17f800 --- /dev/null +++ b/tests/fixtures/more_sources/brush-releases.json @@ -0,0 +1,88 @@ +[ + { + "tag_name": "2.32.29", + "draft": false, + "prerelease": true, + "published_at": "2026-09-26T17:49:28Z", + "assets": [ + { + "id": 591143285, + "name": "OpenBrush_Android_2.32.29.apk", + "size": 314602794, + "browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.29/OpenBrush_Android_2.32.29.apk", + "digest": "sha256:f20361b830803a2bf53e2af648bba59ee17bc4615bde534dbf6c4f0012bbd291" + }, + { + "id": 591143287, + "name": "OpenBrush_Desktop_2.32.29.zip", + "size": 380735034, + "browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.29/OpenBrush_Desktop_2.32.29.zip", + "digest": "sha256:3b680b07ca0b8def579fe194916aa7db4d007c3094c4dea818124776098c9b9a" + }, + { + "id": 591143282, + "name": "OpenBrush_Linux_2.32.29.zip", + "size": 364906490, + "browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.29/OpenBrush_Linux_2.32.29.zip", + "digest": "sha256:e380934f77d2b1441179424193a75f7b4b5793b34761c04cbf2d87bad9f8fc16" + }, + { + "id": 591143283, + "name": "OpenBrush_Mac_2.32.29.dmg", + "size": 373196471, + "browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.29/OpenBrush_Mac_2.32.29.dmg", + "digest": "sha256:5d544d0a64bed1cae65173fcfa7ada069d4f81b42385e806e99cc4427ef3513c" + }, + { + "id": 591143286, + "name": "OpenBrush_Quest_2.32.29.apk", + "size": 314603546, + "browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.29/OpenBrush_Quest_2.32.29.apk", + "digest": "sha256:57cd7b9067689060451494e55dc06276f934a992f5cbbd44d965069903937ba6" + } + ] + }, + { + "tag_name": "2.32.28", + "draft": false, + "prerelease": true, + "published_at": "2026-09-26T14:42:27Z", + "assets": [ + { + "id": 590837298, + "name": "OpenBrush_Android_2.32.28.apk", + "size": 314603450, + "browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.28/OpenBrush_Android_2.32.28.apk", + "digest": "sha256:1a3af1a194c4348ef67c8ea61d9a8258e2490cdfe1f760cbc3c69f2978a6a082" + }, + { + "id": 590837301, + "name": "OpenBrush_Desktop_2.32.28.zip", + "size": 380738236, + "browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.28/OpenBrush_Desktop_2.32.28.zip", + "digest": "sha256:c2de5424bc022951f0e0bdbd652ede549a1e60d9cfbf41c730ea43d16d97262f" + }, + { + "id": 590837297, + "name": "OpenBrush_Linux_2.32.28.zip", + "size": 364907046, + "browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.28/OpenBrush_Linux_2.32.28.zip", + "digest": "sha256:29daf410347b3ca36e47808e31172270df8040ba7decc83be2bdcd51de895e06" + }, + { + "id": 590837296, + "name": "OpenBrush_Mac_2.32.28.dmg", + "size": 373195966, + "browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.28/OpenBrush_Mac_2.32.28.dmg", + "digest": "sha256:2f352d766450c993fdcae8a8552878a6a976d32d675246b63c81bb1b326fd20d" + }, + { + "id": 590837295, + "name": "OpenBrush_Quest_2.32.28.apk", + "size": 314604234, + "browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.28/OpenBrush_Quest_2.32.28.apk", + "digest": "sha256:9a3af6a6e838dd8bd201e549c5570f67db794df940e960390aeeae93235d702e" + } + ] + } +] diff --git a/tests/fixtures/more_sources/itch-author-robots.txt b/tests/fixtures/more_sources/itch-author-robots.txt new file mode 100644 index 0000000..5dacc48 --- /dev/null +++ b/tests/fixtures/more_sources/itch-author-robots.txt @@ -0,0 +1,12 @@ +User-agent: Mediapartners-Google +Disallow: + +User-agent: * +Disallow: /*/download/ +Disallow: /*/rh/ +Disallow: /*/rp/ +Disallow: /-/ + +Sitemap: https://itch.io/sitemap.xml + +# vim: set ft=robots: diff --git a/tests/fixtures/more_sources/itch-feed.txt b/tests/fixtures/more_sources/itch-feed.txt new file mode 100644 index 0000000..87a9e72 --- /dev/null +++ b/tests/fixtures/more_sources/itch-feed.txt @@ -0,0 +1,11 @@ +Tracking-origin controls are not available yet: reversible changes still need an idle-headset test. Use SteamVR’s recenter and room setup for now. A seated origin only affects apps that use seated tracking; it cannot force a game into seated mode.
+None of Frame Control’s features needs these apps. Compatibility reports and account ownership are separate. Paid apps can only be installed here when already owned.
+ +Share this screen with people in the room. Casting shows everything the wearer sees, including private content.
+ +Checking session…
+ +A one-minute warning, then Steam Home. Games stay running: save and pause first. Keep this app open and connected for notifications.
+This is a reminder, not a parental lock. The timer continues on the Frame if you disconnect; a headset restart cancels it. Cancel before changing settings. Set break/check-in to 0 to turn them off.
+Battery, heat and check-in alerts appear on connected companions during a session. Headset warnings and break reminders continue without a companion. iOS may suspend phone notifications in the background. Breaks and check-ins count SteamVR activity, not confirmed wear time.
+SteamGridDB supplies community artwork for sideloaded games. Without a key, Frame Control uses source images and creates the remaining art.
+ + +