of('textures') === [] && !$files->has('textures', $tree)); check('offering a file says it changed something', $files->set('textures', $tree, true) === true); check('and it is offered', $files->has('textures', $tree) && $files->of('textures') === [$tree]); check('offering it again changes nothing', $files->set('textures', $tree, true) === false && $files->of('textures') === [$tree]); $files->set('sounds', $song, true); check('each kind has its own list', $files->of('sounds') === [$song] && !$files->has('sounds', $tree)); check('a new reader finds the same', (new DemoFiles("{$data}/demo-files.json"))->of('textures') === [$tree]); check('the list is not for the web server', (fileperms("{$data}/demo-files.json") & 0o007) === 0); check('taking the offer back', $files->set('textures', $tree, false) === true && $files->of('textures') === []); check('leaves the other kinds', $files->of('sounds') === [$song]); $threw = false; try { $files->set('pixels', $tree, true); } catch (InvalidArgumentException) { $threw = true; } check('a kind that cannot be offered is refused', $threw); // --- Demo copies ----------------------------------------------------------------------------------- $demos = new ReleaseStore("{$data}/demos"); check('without demo copies the list is empty', $demos->list() === []); $store = new Store("{$data}/projects", 'admin'); $platformer = $store->create('Platformer'); $empty = $store->create('Empty project'); $saved = $store->save($platformer['id'], 1, ['id' => $platformer['id'], 'name' => 'Platformer', 'assets' => [['id' => 'a', 'file' => $song]]]); $first = $demos->publish($saved); $demos->publish($empty); $list = $demos->list(); check('the list names every copy, by name', array_column($list, 'name') === ['Empty project', 'Platformer'], $list); check('with its token and when it was made', ReleaseStore::isValidToken($list[1]['token']) && $list[1]['publishedAt'] !== '', $list[1]); check('and nothing of the project itself', !array_key_exists('project', $list[0]) && !array_key_exists('projectId', $list[0])); check('the token loads the frozen copy', $demos->load($first['token'])['project']['assets'][0]['file'] === $song); $store->save($platformer['id'], 2, ['id' => $platformer['id'], 'name' => 'Platformer', 'assets' => []]); check('changing the project leaves the copy as it was', $demos->load($first['token'])['project']['assets'] !== []); $again = $demos->publish($store->load($platformer['id'])); check('updating it keeps the token and takes the new stand', $again['token'] === $first['token'] && $demos->load($first['token'])['project']['assets'] === []); // --- A demo copy uses its files -------------------------------------------------------------------- $store->save($platformer['id'], 3, ['id' => $platformer['id'], 'name' => 'Platformer', 'assets' => [['id' => 'a', 'file' => $song]]]); $demos->publish($store->load($platformer['id'])); $store->save($platformer['id'], 4, ['id' => $platformer['id'], 'name' => 'Platformer', 'assets' => []]); $uses = (new FileUsage($data))->of($song); check('a file only the demo copy still names counts as used', count($uses) === 1 && $uses[0]['id'] === $platformer['id'], $uses); check('and the refusal says so', str_contains(FileUsage::refusal($uses, 'admin', true), 'demo copies')); $demos->takeDown($platformer['id']); check('once the copy is gone, so is the use', (new FileUsage($data))->of($song) === []); exec('rm -rf ' . escapeshellarg($data)); echo $failed === 0 ? "ALL PASSED – {$passed} checks\n" : "{$failed} FAILED, {$passed} passed\n"; exit($failed === 0 ? 0 : 1);