diff --git a/README.md b/README.md index ba0ed95..83ed1c4 100644 --- a/README.md +++ b/README.md @@ -31,19 +31,25 @@ Progress: https://github.com/ipfs/community/discussions/573 - [x] Render empty directories - [x] Resolve `index.html` in a path - `mutable.html` Tests (experimental, some things subject to change) - - [x] JS `fetch('ipfs://CID/example.txt', {method: 'POST'})` - - [x] JS `fetch('ipns://CID/', {method: 'POST'})` - - [x] JS `fetch('ipns://CID/example.txt', {method: 'POST'})` - - [x] JS `fetch('ipfs://CID/', {method: 'POST', body: new FormData})` - - [x] JS `fetch('ipfs://CID/example.txt', {method: 'DELETE'})` - - [x] JS `fetch('ipns://CID/example.txt', {method: 'DELETE'})` + - [x] JS `fetch('ipfs:///example.txt', {method: 'PUT'})` + - [x] JS `fetch('ipfs:///', {method: 'PUT', body: new FormData})` + - [x] JS `fetch('ipfs:///example.txt', {method: 'DELETE'})` + - [x] JS `fetch('ipns://', {method: 'POST', body: })` + - [x] JS `fetch('ipns:///example.txt', {method: 'POST', body: })` + - [x] JS `fetch('ipns://', {method: 'PUT', body: file})` + - [x] JS `fetch('ipns:///example', {method: 'PUT', body: file})` + - [x] JS `fetch('ipns://', {method: 'PUT', body: formdata})` + - [x] JS `fetch('ipns:///example', {method: 'PUT', body: formdata})` + - [x] JS `fetch('ipns:///example.txt', {method: 'DELETE'})` + - [x] JS `fetch('ipns://localhost?key=', {method: 'GET})` + - [x] JS `fetch('ipns://localhost?key=', {method: 'DELETE})` - `dag.html` Tests (experimental, some things subject to change) - - [x] GET `ipfs://CID/?format=CAR` - - [x] GET `ipfs://CID/?format=block` - - [x] GET `ipfs://CID/?format=dag-json` - - [x] GET `ipfs://CID/?format=dag-cbor` - - [x] POST `Content-Type: application/json` `?format=dag-cbor` & GET `?format=dag-cbor` - - [x] POST over existing CID to add to graph + - [x] GET `ipfs:///?format=car` + - [x] GET `ipfs:///?format=raw` + - [x] GET `ipfs:///?format=dag-json` + - [x] GET `ipfs:///?format=dag-cbor` + - [x] POST `Content-Type: application/json` `?format=dag-cbor` & GET `?format=dag-cbor` + - [x] POST over existing CID to add to graph ## Screenshots: diff --git a/mutable-tests.js b/mutable-tests.js index ec75b0f..8dfdfba 100644 --- a/mutable-tests.js +++ b/mutable-tests.js @@ -9,17 +9,19 @@ const TEST_FILE_CONTENT = 'Hello World' const TEST_FILE_NAME = 'example.txt' const OTHER_TEST_FILE_CONTENT = 'Goodby World' const OTHER_TEST_FILE_NAME = 'example2.txt' -const EMPTY_DIR_URL = 'ipfs://bafybeiczsscdsbs7ffqz55asqdf3smv6klcw3gofszvwlyarci47bgf354/' +const EMPTY_DIR_URL = 'ipfs://bafybeiczsscdsbs7ffqz55asqdf3smv6klcw3gofszvwlyarci47bgf354' + +setup({ timeout_multiplier: 2 }) promise_test(async (t) => { - const postResponse = await fetch(`ipfs://${TEST_FILE_NAME}`, { - method: 'POST', + const putResponse = await fetch(`${EMPTY_DIR_URL}/${TEST_FILE_NAME}`, { + method: 'PUT', body: TEST_FILE_CONTENT }) - assert_true(postResponse.ok, 'Able to POST') + assert_true(putResponse.status == 201, 'Able to PUT') - const url = await postResponse.text() + const url = await putResponse.headers.get("Location") assert_true(url.startsWith('ipfs://'), 'Got an IPFS url for the content') assert_true(url.endsWith(TEST_FILE_NAME), 'URL ends with the file name') @@ -39,28 +41,28 @@ promise_test(async (t) => { const listing = await listingRequest.text() assert_true(listing.includes(TEST_FILE_NAME), 'File shows up in parent folder') -}, 'POST text file to IPFS without infohash') +}, 'PUT text file to IPFS inside empty dir') promise_test(async (t) => { - const firstFileResponse = await fetch(`ipfs://${TEST_FILE_NAME}`, { - method: 'POST', + const firstFileResponse = await fetch(`${EMPTY_DIR_URL}/${TEST_FILE_NAME}`, { + method: 'PUT', body: TEST_FILE_CONTENT }) - assert_true(firstFileResponse.ok, 'Able to post first file') + assert_true(firstFileResponse.status == 201, 'Able to PUT first file') - const url = await firstFileResponse.text() + const url = await firstFileResponse.headers.get("Location") const base = new URL('./', url).href const secondFileResponse = await fetch(new URL(`./${OTHER_TEST_FILE_NAME}`, base).href, { - method: 'POST', + method: 'PUT', body: OTHER_TEST_FILE_CONTENT }) - assert_true(secondFileResponse.ok, 'Able to post second file') + assert_true(secondFileResponse.status == 201, 'Able to post second file') - const secondUrl = await secondFileResponse.text() + const secondUrl = await secondFileResponse.headers.get("Location") assert_true(secondUrl.startsWith('ipfs://'), 'Got an IPFS url for the content') assert_true(secondUrl.endsWith(OTHER_TEST_FILE_NAME), 'URL ends with the file name') @@ -73,28 +75,28 @@ promise_test(async (t) => { assert_true(listing.includes(TEST_FILE_NAME), 'First file shows up in parent folder') assert_true(listing.includes(OTHER_TEST_FILE_NAME), 'Second file shows up in parent folder') -}, 'POST text file to existing infohash') +}, 'PUT text file to existing infohash') promise_test(async (t) => { - const firstFileResponse = await fetch(`ipfs://${TEST_FILE_NAME}`, { - method: 'POST', + const firstFileResponse = await fetch(`${EMPTY_DIR_URL}/${TEST_FILE_NAME}`, { + method: 'PUT', body: TEST_FILE_CONTENT }) - assert_true(firstFileResponse.ok, 'Able to post first file') + assert_true(firstFileResponse.status == 201, 'Able to PUT first file') - const url = await firstFileResponse.text() + const url = await firstFileResponse.headers.get("Location") const base = new URL('./', url).href const secondFileResponse = await fetch(new URL(`./${OTHER_TEST_FILE_NAME}`, base).href, { - method: 'POST', + method: 'PUT', body: OTHER_TEST_FILE_CONTENT }) - assert_true(secondFileResponse.ok, 'Able to post second file') + assert_true(secondFileResponse.status == 201, 'Able to PUT second file') - const secondUrl = await secondFileResponse.text() + const secondUrl = await secondFileResponse.headers.get("Location") const secondBase = new URL('./', secondUrl).href @@ -109,9 +111,9 @@ promise_test(async (t) => { method: 'DELETE' }) - assert_true(deleteResponse.ok, 'Able to DELETE file URL') + assert_true(deleteResponse.status == 201, 'Able to DELETE file URL') - const finalUrl = await deleteResponse.text() + const finalUrl = await deleteResponse.headers.get("Location") assert_true(finalUrl.startsWith('ipfs://'), 'Got an IPFS url for the content') assert_true(finalUrl.endsWith('/'), 'URL ends with a / to signify a directory') @@ -125,37 +127,158 @@ promise_test(async (t) => { }, 'DELETE file from an infohash') promise_test(async (t) => { - const firstFileResponse = await fetch(`ipfs://${TEST_FILE_NAME}`, { + const key = `compliance-suite-${Date.now()}` + const createKey = await fetch(`ipns://localhost?key=${key}`, { method: 'POST', + }) + assert_true(createKey.status == 201, 'Able to create key') + const keyURL = createKey.headers.get("Location") + + const firstFileResponse = await fetch(`${keyURL}${TEST_FILE_NAME}`, { + method: 'PUT', body: TEST_FILE_CONTENT }) + assert_true(firstFileResponse.status == 200, 'Able to PUT first file') - assert_true(firstFileResponse.ok, 'Able to post first file') + const base = new URL('./', keyURL).href - const url = await firstFileResponse.text() + const secondUrl = new URL(`./${OTHER_TEST_FILE_NAME}`, base).href - const base = new URL('./', url).href + const secondFileResponse = await fetch(secondUrl, { + method: 'PUT', + body: OTHER_TEST_FILE_CONTENT + }) + + assert_true(secondFileResponse.status == 200, 'Able to PUT second file') + + const listingRequest = await fetch(base) + + const listing = await listingRequest.text() + + assert_true(listing.includes(TEST_FILE_NAME), 'First file shows up in parent folder') + assert_true(listing.includes(OTHER_TEST_FILE_NAME), 'Second file shows up in parent folder') + + const deleteResponse = await fetch(secondUrl, { + method: 'DELETE' + }) + + assert_true(deleteResponse.status == 200, 'Able to DELETE file URL') + + const finalListingRequest = await fetch(base) + + const finalListing = await finalListingRequest.text() + + assert_true(finalListing.includes(TEST_FILE_NAME), 'First file still shows up in folder') + assert_true(!finalListing.includes(OTHER_TEST_FILE_NAME), 'Second file no longer shows up in folder') +}, 'DELETE file from IPNS', { timeout: 120 * 1000 }) + +promise_test(async (t) => { + const firstFileResponse = await fetch(`${EMPTY_DIR_URL}/${TEST_FILE_NAME}`, { + method: 'PUT', + body: TEST_FILE_CONTENT + }) + + assert_true(firstFileResponse.status == 201, 'Able to post first file') + + const url = await firstFileResponse.headers.get("Location") - const ipnsResponse = await fetch('ipns://compliance-suite-example/', { + const key = `compliance-suite-${Date.now()}` + const createKey = await fetch(`ipns://localhost?key=${key}`, { method: 'POST', - body: base }) + assert_true(createKey.status == 201, 'Able to create key') + const keyURL = createKey.headers.get("Location") - assert_true(ipnsResponse.ok, 'Able to POST url to ipns') + const ipnsResponse = await fetch(keyURL, { + method: 'POST', + body: url + }) - const ipnsUrl = await ipnsResponse.text() + assert_true(ipnsResponse.status == 200, 'Able to POST url to ipns') - assert_true(ipnsUrl.startsWith('ipns://'), 'Got an IPNS URL') - assert_true(ipnsUrl.endsWith('/'), 'Ends as a directory') + const getResponse = await fetch(keyURL) + const text = await getResponse.text() - const listingRequest = await fetch(ipnsUrl) + assert_equals(text, TEST_FILE_CONTENT, 'Got content back out') +}, 'POST IPFS URL to IPNS') - assert_true(listingRequest.ok, 'Able to GET from IPNS') +promise_test(async (t) => { + const key = `compliance-suite-${Date.now()}` + const createKey = await fetch(`ipns://localhost?key=${key}`, { + method: 'POST', + }) + assert_true(createKey.status == 201, 'Able to create key') + const keyURL = createKey.headers.get("Location") + + const ipnsResponse = await fetch(keyURL, { + method: 'PUT', + body: TEST_FILE_CONTENT + }) + + assert_true(ipnsResponse.status == 200, 'Able to PUT file to ipns') + + const getResponse = await fetch(keyURL) + const text = await getResponse.text() + + assert_equals(text, TEST_FILE_CONTENT, 'Got content back out') +}, 'PUT IPFS file to IPNS', { timeout: 120 * 1000 }) + +promise_test(async (t) => { + const key = `compliance-suite-${Date.now()}` + const createKey = await fetch(`ipns://localhost?key=${key}`, { + method: 'POST', + }) + assert_true(createKey.status == 201, 'Able to create key') + const keyURL = createKey.headers.get("Location") + + const ipnsResponse = await fetch(`${keyURL}test`, { + method: 'PUT', + body: TEST_FILE_CONTENT + }) + + assert_true(ipnsResponse.status == 200, 'Able to PUT url to ipns') + + const getResponse = await fetch(`${keyURL}test`) + const text = await getResponse.text() + + assert_equals(text, TEST_FILE_CONTENT, 'Got content back out') +}, 'PUT IPFS file to IPNS subpath') + +promise_test(async (t) => { + const formData = new FormData() + + formData.append('file', new Blob([TEST_FILE_CONTENT]), TEST_FILE_NAME) + formData.append('file', new Blob([OTHER_TEST_FILE_CONTENT]), OTHER_TEST_FILE_NAME) + + const key = `compliance-suite-${Date.now()}` + const createKey = await fetch(`ipns://localhost?key=${key}`, { + method: 'POST', + }) + assert_true(createKey.status == 201, 'Able to create key') + const keyURL = createKey.headers.get("Location") + + const putResponse = await fetch(keyURL, { + method: 'PUT', + body: formData + }) + + assert_true(putResponse.status == 200, 'Able to PUT') + + const getResponse = await fetch(`${keyURL}${TEST_FILE_NAME}`) + + assert_true(getResponse.status === 200, 'Able to get uploaded file') + + const text = await getResponse.text() + + assert_equals(text, TEST_FILE_CONTENT, 'Got content back out') + + const listingRequest = await fetch(keyURL) const listing = await listingRequest.text() - assert_true(listing.includes(TEST_FILE_NAME), 'File shows up in IPNS folder') -}, 'POST IPFS URL to IPNS') + assert_true(listing.includes(TEST_FILE_NAME), 'File shows up in parent folder') + assert_true(listing.includes(OTHER_TEST_FILE_NAME), 'Other file shows up in parent folder') +}, 'PUT FormData to IPNS', { timeout: 120 * 1000 }) promise_test(async (t) => { const formData = new FormData() @@ -163,14 +286,50 @@ promise_test(async (t) => { formData.append('file', new Blob([TEST_FILE_CONTENT]), TEST_FILE_NAME) formData.append('file', new Blob([OTHER_TEST_FILE_CONTENT]), OTHER_TEST_FILE_NAME) - const postResponse = await fetch(EMPTY_DIR_URL, { + const key = `compliance-suite-${Date.now()}` + const createKey = await fetch(`ipns://localhost?key=${key}`, { method: 'POST', + }) + assert_true(createKey.status == 201, 'Able to create key') + const keyURL = createKey.headers.get("Location") + + const putResponse = await fetch(new URL("/test", keyURL).href, { + method: 'PUT', body: formData }) - assert_true(postResponse.ok, 'Able to POST') + assert_true(putResponse.status == 200, 'Able to PUT') + + const getResponse = await fetch(new URL(`/test/${TEST_FILE_NAME}`, keyURL).href) + + assert_true(getResponse.ok, 'Able to use URL from POST') + + const text = await getResponse.text() + + assert_equals(text, TEST_FILE_CONTENT, 'Got content back out') + + const listingRequest = await fetch(new URL('/test', keyURL).href) + + const listing = await listingRequest.text() - const url = await postResponse.text() + assert_true(listing.includes(TEST_FILE_NAME), 'File shows up in parent folder') + assert_true(listing.includes(OTHER_TEST_FILE_NAME), 'Other file shows up in parent folder') +}, 'PUT FormData to IPNS subpath', { timeout: 120 * 1000 }) + +promise_test(async (t) => { + const formData = new FormData() + + formData.append('file', new Blob([TEST_FILE_CONTENT]), TEST_FILE_NAME) + formData.append('file', new Blob([OTHER_TEST_FILE_CONTENT]), OTHER_TEST_FILE_NAME) + + const putResponse = await fetch(EMPTY_DIR_URL, { + method: 'PUT', + body: formData + }) + + assert_true(putResponse.status == 201, 'Able to PUT') + + const url = await putResponse.headers.get("Location") assert_true(url.startsWith('ipfs://'), 'Got an IPFS url for the content') assert_true(url.endsWith('/'), 'URL is a directory') @@ -189,41 +348,53 @@ promise_test(async (t) => { assert_true(listing.includes(TEST_FILE_NAME), 'File shows up in parent folder') assert_true(listing.includes(OTHER_TEST_FILE_NAME), 'Other file shows up in parent folder') -}, 'POST FormData with files to IPFS') +}, 'PUT FormData with files to IPFS') promise_test(async (t) => { - const firstFileResponse = await fetch(`ipfs://${TEST_FILE_NAME}`, { - method: 'POST', + const firstFileResponse = await fetch(`${EMPTY_DIR_URL}/${TEST_FILE_NAME}`, { + method: 'PUT', body: TEST_FILE_CONTENT }) - assert_true(firstFileResponse.ok, 'Able to post first file') + assert_true(firstFileResponse.status == 201, 'Able to PUT first file') - const url = await firstFileResponse.text() + const url = await firstFileResponse.headers.get("Location") const base = new URL('./', url).href - // Note that the key pet name is a temporary measure - // This should be replaced with public keys once a key generation standard is figured out - const ipnsResponse = await fetch('ipns://compliance-suite-example2/', { + const key = `compliance-suite-${Date.now()}` + const createKey = await fetch(`ipns://localhost?key=${key}`, { + method: 'POST', + }) + assert_true(createKey.status == 201, 'Able to create key') + const keyURL = createKey.headers.get("Location") + + const ipnsResponse = await fetch(`${keyURL}${TEST_FILE_NAME}`, { method: 'POST', body: base }) - assert_true(ipnsResponse.ok, 'Able to POST url to ipns') + assert_true(ipnsResponse.status == 200, 'Able to POST url to ipns') + + const secondFileResponse = await fetch(`${EMPTY_DIR_URL}/${OTHER_TEST_FILE_NAME}`, { + method: 'PUT', + body: OTHER_TEST_FILE_CONTENT + }) - const ipnsUrl = await ipnsResponse.text() + assert_true(secondFileResponse.status == 201, 'Able to PUT second file') - const otherFileURL = new URL(`/${OTHER_TEST_FILE_NAME}`, ipnsUrl) + const url2 = await secondFileResponse.headers.get("Location") - const secondFileResponse = await fetch(otherFileURL, { + const base2 = new URL('./', url2).href + + const ipnsResponse2 = await fetch(`${keyURL}${OTHER_TEST_FILE_NAME}`, { method: 'POST', - body: OTHER_TEST_FILE_CONTENT + body: base2 }) - assert_true(secondFileResponse.ok, 'Able to POST file to existing IPNS path') + assert_true(ipnsResponse2.status == 200, 'Able to POST url to existing IPNS key') - const listingRequest = await fetch(ipnsUrl) + const listingRequest = await fetch(keyURL) assert_true(listingRequest.ok, 'Able to GET from IPNS') @@ -231,4 +402,50 @@ promise_test(async (t) => { assert_true(listing.includes(TEST_FILE_NAME), 'First file shows up in IPNS folder') assert_true(listing.includes(OTHER_TEST_FILE_NAME), 'Second file shows up in IPNS folder') -}, 'POST text file to IPNS with existing data') +}, 'POST url to IPNS with existing data') + +promise_test(async (t) => { + const key = `compliance-suite-${Date.now()}` + const getKey = await fetch(`ipns://localhost?key=${key}`, { + method: 'GET', + }) + assert_true(getKey.status == 404, "key doesn't exist yet") + + const createKey = await fetch(`ipns://localhost?key=${key}`, { + method: 'POST', + }) + assert_true(createKey.status == 201, 'Able to create key') + const keyURL = createKey.headers.get("Location") + + // Upload file so key resolves when GET is done later + const ipnsResponse = await fetch(keyURL, { + method: 'PUT', + body: TEST_FILE_CONTENT + }) + assert_true(ipnsResponse.status == 200, 'Able to PUT url to ipns') + + const getKey2 = await fetch(`ipns://localhost?key=${key}`, { + method: 'GET', + }) + // Should redirect to key ^ + assert_true(getKey2.status == 200, '200, redirected to key') + assert_true(new URL(getKey2.url).hostname == new URL(keyURL).hostname, 'redirected to correct key URL') +}, 'GET IPNS key') + +promise_test(async (t) => { + const key = `compliance-suite-${Date.now()}` + const createKey = await fetch(`ipns://localhost?key=${key}`, { + method: 'POST', + }) + assert_true(createKey.status == 201, 'Able to create key') + + const deleteKey = await fetch(`ipns://localhost?key=${key}`, { + method: 'DELETE', + }) + assert_true(deleteKey.status == 200, '200, deleted key') + + const getKey = await fetch(`ipns://localhost?key=${key}`, { + method: 'GET', + }) + assert_true(getKey.status == 404, "404, key doesn't exist") +}, 'DELETE IPNS key') diff --git a/mutable.html b/mutable.html index 1aa7e29..34a9264 100644 --- a/mutable.html +++ b/mutable.html @@ -1,6 +1,6 @@ IPFS Protocol Compliance Suite - Mutability - + diff --git a/publish-files.js b/publish-files.js index f46766e..5e8f053 100755 --- a/publish-files.js +++ b/publish-files.js @@ -63,7 +63,7 @@ async function run () { await fs.mkdir(emptyDir, { recursive: true }) console.log('Uploading to IPFS') - const { stdout: output } = await exec('ipfs add ./files/ --cid-version=1 --raw-leaves=false -r') + const { stdout: output } = await exec('ipfs add ./files/ --cid-version=1 --raw-leaves=false -r --api /ip4/127.0.0.1/tcp/5001') const lines = output.split(/\r?\n/)