storage.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /*
  2. * This file is part of the storage node for the Joystream project.
  3. * Copyright (C) 2019 Joystream Contributors
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. 'use strict'
  19. const chai = require('chai')
  20. const chaiAsPromised = require('chai-as-promised')
  21. chai.use(chaiAsPromised)
  22. const expect = chai.expect
  23. const fs = require('fs')
  24. const { Storage } = require('@joystream/storage-node-backend')
  25. const IPFS_CID_REGEX = /^Qm[1-9A-HJ-NP-Za-km-z]{44}$/
  26. function write(store, contentId, contents, callback) {
  27. store
  28. .open(contentId, 'w')
  29. .then((stream) => {
  30. stream.on('finish', () => {
  31. stream.commit()
  32. })
  33. stream.on('committed', callback)
  34. if (!stream.write(contents)) {
  35. stream.once('drain', () => stream.end())
  36. } else {
  37. process.nextTick(() => stream.end())
  38. }
  39. })
  40. .catch((err) => {
  41. expect.fail(err)
  42. })
  43. }
  44. function readAll(stream) {
  45. return new Promise((resolve, reject) => {
  46. const chunks = []
  47. stream.on('data', (chunk) => chunks.push(chunk))
  48. stream.on('end', () => resolve(Buffer.concat(chunks)))
  49. stream.on('error', (err) => reject(err))
  50. stream.resume()
  51. })
  52. }
  53. function createKnownObject(contentId, contents, callback) {
  54. let hash
  55. const store = Storage.create({
  56. resolve_content_id: () => {
  57. return hash
  58. },
  59. })
  60. write(store, contentId, contents, (theHash) => {
  61. hash = theHash
  62. callback(store, hash)
  63. })
  64. }
  65. describe('storage/storage', () => {
  66. let storage
  67. before(async () => {
  68. storage = await Storage.create({ timeout: 1900 })
  69. })
  70. describe('open()', () => {
  71. it('can write a stream', (done) => {
  72. write(storage, 'foobar', 'test-content', (hash) => {
  73. expect(hash).to.not.be.undefined
  74. expect(hash).to.match(IPFS_CID_REGEX)
  75. done()
  76. })
  77. })
  78. // it('detects the MIME type of a write stream', (done) => {
  79. // const contents = fs.readFileSync('../../storage-node_new.svg')
  80. // storage
  81. // .open('mime-test', 'w')
  82. // .then((stream) => {
  83. // let fileInfo
  84. // stream.on('fileInfo', (info) => {
  85. // // Could filter & abort here now, but we're just going to set this,
  86. // // and expect it to be set later...
  87. // fileInfo = info
  88. // })
  89. //
  90. // stream.on('finish', () => {
  91. // stream.commit()
  92. // })
  93. //
  94. // stream.on('committed', () => {
  95. // // ... if fileInfo is not set here, there's an issue.
  96. // expect(fileInfo).to.have.property('mimeType', 'application/xml')
  97. // expect(fileInfo).to.have.property('ext', 'xml')
  98. // done()
  99. // })
  100. //
  101. // if (!stream.write(contents)) {
  102. // stream.once('drain', () => stream.end())
  103. // } else {
  104. // process.nextTick(() => stream.end())
  105. // }
  106. // })
  107. // .catch((err) => {
  108. // expect.fail(err)
  109. // })
  110. // })
  111. it('can read a stream', (done) => {
  112. const contents = 'test-for-reading'
  113. createKnownObject('foobar', contents, (store) => {
  114. store
  115. .open('foobar', 'r')
  116. .then(async (stream) => {
  117. const data = await readAll(stream)
  118. expect(Buffer.compare(data, Buffer.from(contents))).to.equal(0)
  119. done()
  120. })
  121. .catch((err) => {
  122. expect.fail(err)
  123. })
  124. })
  125. })
  126. it('detects the MIME type of a read stream', (done) => {
  127. const contents = fs.readFileSync('../../storage-node_new.svg')
  128. createKnownObject('foobar', contents, (store) => {
  129. store
  130. .open('foobar', 'r')
  131. .then(async (stream) => {
  132. const data = await readAll(stream)
  133. expect(contents.length).to.equal(data.length)
  134. expect(Buffer.compare(data, contents)).to.equal(0)
  135. expect(stream).to.have.property('fileInfo')
  136. // application/xml+svg would be better, but this is good-ish.
  137. expect(stream.fileInfo).to.have.property('mimeType', 'application/xml')
  138. expect(stream.fileInfo).to.have.property('ext', 'xml')
  139. done()
  140. })
  141. .catch((err) => {
  142. expect.fail(err)
  143. })
  144. })
  145. })
  146. it('provides default MIME type for read streams', (done) => {
  147. const contents = 'test-for-reading'
  148. createKnownObject('foobar', contents, (store) => {
  149. store
  150. .open('foobar', 'r')
  151. .then(async (stream) => {
  152. const data = await readAll(stream)
  153. expect(Buffer.compare(data, Buffer.from(contents))).to.equal(0)
  154. expect(stream.fileInfo).to.have.property('mimeType', 'application/octet-stream')
  155. expect(stream.fileInfo).to.have.property('ext', 'bin')
  156. done()
  157. })
  158. .catch((err) => {
  159. expect.fail(err)
  160. })
  161. })
  162. })
  163. })
  164. describe('stat()', () => {
  165. it('times out for unknown content', async () => {
  166. const content = Buffer.from('this-should-not-exist')
  167. const x = await storage.ipfs.add(content, { onlyHash: true })
  168. const hash = x[0].hash
  169. // Try to stat this entry, it should timeout.
  170. expect(storage.stat(hash)).to.eventually.be.rejectedWith('timed out')
  171. })
  172. it('returns stats for a known object', (done) => {
  173. const content = 'stat-test'
  174. const expectedSize = content.length
  175. createKnownObject('foobar', content, (store, hash) => {
  176. expect(store.stat(hash)).to.eventually.have.property('size', expectedSize)
  177. done()
  178. })
  179. })
  180. })
  181. describe('size()', () => {
  182. it('times out for unknown content', async () => {
  183. const content = Buffer.from('this-should-not-exist')
  184. const x = await storage.ipfs.add(content, { onlyHash: true })
  185. const hash = x[0].hash
  186. // Try to stat this entry, it should timeout.
  187. expect(storage.size(hash)).to.eventually.be.rejectedWith('timed out')
  188. })
  189. it('returns the size of a known object', (done) => {
  190. createKnownObject('foobar', 'stat-test', (store, hash) => {
  191. expect(store.size(hash)).to.eventually.equal(15)
  192. done()
  193. })
  194. })
  195. })
  196. })