logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://hacktivis.me/git/pleroma-fe.git

entity_normalizer.spec.js (12914B)


  1. import { parseStatus, parseUser, parseNotification, parseLinkHeaderPagination } from '../../../../../src/services/entity_normalizer/entity_normalizer.service.js'
  2. import mastoapidata from '../../../../fixtures/mastoapi.json'
  3. import qvitterapidata from '../../../../fixtures/statuses.json'
  4. const makeMockStatusQvitter = (overrides = {}) => {
  5. return Object.assign({
  6. activity_type: 'post',
  7. attachments: [],
  8. attentions: [],
  9. created_at: 'Tue Jan 15 13:57:56 +0000 2019',
  10. external_url: 'https://ap.example/whatever',
  11. fave_num: 1,
  12. favorited: false,
  13. id: '10335970',
  14. in_reply_to_ostatus_uri: null,
  15. in_reply_to_profileurl: null,
  16. in_reply_to_screen_name: null,
  17. in_reply_to_status_id: null,
  18. in_reply_to_user_id: null,
  19. is_local: false,
  20. is_post_verb: true,
  21. possibly_sensitive: false,
  22. repeat_num: 0,
  23. repeated: false,
  24. statusnet_conversation_id: '16300488',
  25. summary: null,
  26. tags: [],
  27. text: 'haha benis',
  28. uri: 'https://ap.example/whatever',
  29. user: makeMockUserQvitter(),
  30. visibility: 'public'
  31. }, overrides)
  32. }
  33. const makeMockUserQvitter = (overrides = {}) => {
  34. return Object.assign({
  35. background_image: null,
  36. cover_photo: '',
  37. created_at: 'Mon Jan 14 13:56:51 +0000 2019',
  38. default_scope: 'public',
  39. description: 'ebin',
  40. description_html: '<p>ebin</p>',
  41. favourites_count: 0,
  42. fields: [],
  43. followers_count: 1,
  44. following: true,
  45. follows_you: true,
  46. friends_count: 1,
  47. id: '60717',
  48. is_local: false,
  49. locked: false,
  50. name: 'Spurdo :ebin:',
  51. name_html: 'Spurdo <img src="whatever">',
  52. no_rich_text: false,
  53. pleroma: { confirmation_pending: false, tags: [] },
  54. profile_image_url: 'https://ap.example/whatever',
  55. profile_image_url_https: 'https://ap.example/whatever',
  56. profile_image_url_original: 'https://ap.example/whatever',
  57. profile_image_url_profile_size: 'https://ap.example/whatever',
  58. rights: { delete_others_notice: false },
  59. screen_name: 'spurdo@ap.example',
  60. statuses_count: 46,
  61. statusnet_blocking: false,
  62. statusnet_profile_url: ''
  63. }, overrides)
  64. }
  65. const makeMockUserMasto = (overrides = {}) => {
  66. return Object.assign({
  67. acct: 'hj',
  68. avatar:
  69. 'https://shigusegubu.club/media/1657b945-8d5b-4ce6-aafb-4c3fc5772120/8ce851029af84d55de9164e30cc7f46d60cbf12eee7e96c5c0d35d9038ddade1.png',
  70. avatar_static:
  71. 'https://shigusegubu.club/media/1657b945-8d5b-4ce6-aafb-4c3fc5772120/8ce851029af84d55de9164e30cc7f46d60cbf12eee7e96c5c0d35d9038ddade1.png',
  72. bot: false,
  73. created_at: '2017-12-17T21:54:14.000Z',
  74. display_name: 'whatever whatever whatever witch',
  75. emojis: [],
  76. fields: [],
  77. followers_count: 705,
  78. following_count: 326,
  79. header:
  80. 'https://shigusegubu.club/media/7ab024d9-2a8a-4fbc-9ce8-da06756ae2db/6aadefe4e264133bc377ab450e6b045b6f5458542a5c59e6c741f86107f0388b.png',
  81. header_static:
  82. 'https://shigusegubu.club/media/7ab024d9-2a8a-4fbc-9ce8-da06756ae2db/6aadefe4e264133bc377ab450e6b045b6f5458542a5c59e6c741f86107f0388b.png',
  83. id: '1',
  84. locked: false,
  85. note:
  86. 'Volatile Internet Weirdo. Name pronounced as Hee Jay. JS and Java dark arts mage, Elixir trainee. I love sampo and lain. Matrix is <span><a data-user="1" href="https://shigusegubu.club/users/hj">@<span>hj</span></a></span>:matrix.heldscal.la Pronouns are whatever. Do not DM me unless it\'s truly private matter and you\'re instance\'s admin or you risk your DM to be reposted publicly.Wish i was Finnish girl.',
  87. pleroma: { confirmation_pending: false, tags: null },
  88. source: { note: '', privacy: 'public', sensitive: false },
  89. statuses_count: 41775,
  90. url: 'https://shigusegubu.club/users/hj',
  91. username: 'hj'
  92. }, overrides)
  93. }
  94. const makeMockStatusMasto = (overrides = {}) => {
  95. return Object.assign({
  96. account: makeMockUserMasto(),
  97. application: { name: 'Web', website: null },
  98. content:
  99. '<span><a data-user="14660" href="https://pleroma.soykaf.com/users/sampo">@<span>sampo</span></a></span> god i wish i was there',
  100. created_at: '2019-01-17T16:29:23.000Z',
  101. emojis: [],
  102. favourited: false,
  103. favourites_count: 1,
  104. id: '10423476',
  105. in_reply_to_account_id: '14660',
  106. in_reply_to_id: '10423197',
  107. language: null,
  108. media_attachments: [],
  109. mentions: [
  110. {
  111. acct: 'sampo@pleroma.soykaf.com',
  112. id: '14660',
  113. url: 'https://pleroma.soykaf.com/users/sampo',
  114. username: 'sampo'
  115. }
  116. ],
  117. muted: false,
  118. reblog: null,
  119. reblogged: false,
  120. reblogs_count: 0,
  121. replies_count: 0,
  122. sensitive: false,
  123. spoiler_text: '',
  124. tags: [],
  125. uri: 'https://shigusegubu.club/objects/16033fbb-97c0-4f0e-b834-7abb92fb8639',
  126. url: 'https://shigusegubu.club/objects/16033fbb-97c0-4f0e-b834-7abb92fb8639',
  127. visibility: 'public',
  128. pleroma: {
  129. local: true
  130. }
  131. }, overrides)
  132. }
  133. const makeMockNotificationQvitter = (overrides = {}) => {
  134. return Object.assign({
  135. notice: makeMockStatusQvitter(),
  136. ntype: 'follow',
  137. from_profile: makeMockUserQvitter(),
  138. is_seen: 0,
  139. id: 123
  140. }, overrides)
  141. }
  142. const makeMockEmojiMasto = (overrides = [{}]) => {
  143. return [
  144. Object.assign({
  145. shortcode: 'image',
  146. static_url: 'https://example.com/image.png',
  147. url: 'https://example.com/image.png',
  148. visible_in_picker: false
  149. }, overrides[0]),
  150. Object.assign({
  151. shortcode: 'thinking',
  152. static_url: 'https://example.com/think.png',
  153. url: 'https://example.com/think.png',
  154. visible_in_picker: false
  155. }, overrides[1])
  156. ]
  157. }
  158. describe('API Entities normalizer', () => {
  159. describe('parseStatus', () => {
  160. describe('QVitter preprocessing', () => {
  161. it('doesn\'t blow up', () => {
  162. const parsed = qvitterapidata.map(parseStatus)
  163. expect(parsed.length).to.eq(qvitterapidata.length)
  164. })
  165. it('identifies favorites', () => {
  166. const fav = {
  167. uri: 'tag:soykaf.com,2016-08-21:fave:2558:note:339495:2016-08-21T16:54:04+00:00',
  168. is_post_verb: false
  169. }
  170. const mastoFav = {
  171. uri: 'tag:mastodon.social,2016-11-27:objectId=73903:objectType=Favourite',
  172. is_post_verb: false
  173. }
  174. expect(parseStatus(makeMockStatusQvitter(fav))).to.have.property('type', 'favorite')
  175. expect(parseStatus(makeMockStatusQvitter(mastoFav))).to.have.property('type', 'favorite')
  176. })
  177. it('processes repeats correctly', () => {
  178. const post = makeMockStatusQvitter({ retweeted_status: null, id: 'deadbeef' })
  179. const repeat = makeMockStatusQvitter({ retweeted_status: post, is_post_verb: false, id: 'foobar' })
  180. const parsedPost = parseStatus(post)
  181. const parsedRepeat = parseStatus(repeat)
  182. expect(parsedPost).to.have.property('type', 'status')
  183. expect(parsedRepeat).to.have.property('type', 'retweet')
  184. expect(parsedRepeat).to.have.property('retweeted_status')
  185. expect(parsedRepeat).to.have.nested.property('retweeted_status.id', 'deadbeef')
  186. })
  187. it('sets nsfw for statuses with the #nsfw tag', () => {
  188. const safe = makeMockStatusQvitter({ id: '1', text: 'Hello oniichan' })
  189. const nsfw = makeMockStatusQvitter({ id: '1', text: 'Hello oniichan #nsfw' })
  190. expect(parseStatus(safe).nsfw).to.eq(false)
  191. expect(parseStatus(nsfw).nsfw).to.eq(true)
  192. })
  193. it('leaves existing nsfw settings alone', () => {
  194. const nsfw = makeMockStatusQvitter({ id: '1', text: 'Hello oniichan #nsfw', nsfw: false })
  195. expect(parseStatus(nsfw).nsfw).to.eq(false)
  196. })
  197. })
  198. describe('Mastoapi preprocessing and converting', () => {
  199. it('doesn\'t blow up', () => {
  200. const parsed = mastoapidata.map(parseStatus)
  201. expect(parsed.length).to.eq(mastoapidata.length)
  202. })
  203. it('processes repeats correctly', () => {
  204. const post = makeMockStatusMasto({ reblog: null, id: 'deadbeef' })
  205. const repeat = makeMockStatusMasto({ reblog: post, id: 'foobar' })
  206. const parsedPost = parseStatus(post)
  207. const parsedRepeat = parseStatus(repeat)
  208. expect(parsedPost).to.have.property('type', 'status')
  209. expect(parsedRepeat).to.have.property('type', 'retweet')
  210. expect(parsedRepeat).to.have.property('retweeted_status')
  211. expect(parsedRepeat).to.have.nested.property('retweeted_status.id', 'deadbeef')
  212. })
  213. })
  214. })
  215. // Statuses generally already contain some info regarding users and there's nearly 1:1 mapping, so very little to test
  216. describe('parseUsers (MastoAPI)', () => {
  217. it('sets correct is_local for users depending on their screen_name', () => {
  218. const local = makeMockUserMasto({ acct: 'foo' })
  219. const remote = makeMockUserMasto({ acct: 'foo@bar.baz' })
  220. expect(parseUser(local)).to.have.property('is_local', true)
  221. expect(parseUser(remote)).to.have.property('is_local', false)
  222. })
  223. it('removes html tags from user profile fields', () => {
  224. const user = makeMockUserMasto({ emojis: makeMockEmojiMasto(), fields: [{ name: 'user', value: '<a rel="me" href="https://example.com/@user">@user</a>' }] })
  225. const parsedUser = parseUser(user)
  226. expect(parsedUser).to.have.property('fields_text').to.be.an('array')
  227. const field = parsedUser.fields_text[0]
  228. expect(field).to.have.property('name').that.equal('user')
  229. expect(field).to.have.property('value').that.equal('@user')
  230. })
  231. it('adds hide_follows and hide_followers user settings', () => {
  232. const user = makeMockUserMasto({ pleroma: { hide_followers: true, hide_follows: false, hide_followers_count: false, hide_follows_count: true } })
  233. expect(parseUser(user)).to.have.property('hide_followers', true)
  234. expect(parseUser(user)).to.have.property('hide_follows', false)
  235. expect(parseUser(user)).to.have.property('hide_followers_count', false)
  236. expect(parseUser(user)).to.have.property('hide_follows_count', true)
  237. })
  238. it('converts IDN to unicode and marks it as internatonal', () => {
  239. const user = makeMockUserMasto({ acct: 'lain@xn--lin-6cd.com' })
  240. expect(parseUser(user)).to.have.property('screen_name_ui').that.equal('lain@lаin.com')
  241. expect(parseUser(user)).to.have.property('screen_name_ui_contains_non_ascii').that.equal(true)
  242. })
  243. })
  244. // We currently use QvitterAPI notifications only, and especially due to MastoAPI lacking is_seen, support for MastoAPI
  245. // is more of an afterthought
  246. describe('parseNotifications (QvitterAPI)', () => {
  247. it('correctly normalizes data to FE\'s format', () => {
  248. const notif = makeMockNotificationQvitter({
  249. id: 123,
  250. notice: makeMockStatusQvitter({ id: 444 }),
  251. from_profile: makeMockUserQvitter({ id: 'spurdo' })
  252. })
  253. expect(parseNotification(notif)).to.have.property('id', 123)
  254. expect(parseNotification(notif)).to.have.property('seen', false)
  255. expect(parseNotification(notif)).to.have.nested.property('status.id', '444')
  256. expect(parseNotification(notif)).to.have.nested.property('action.id', '444')
  257. expect(parseNotification(notif)).to.have.nested.property('from_profile.id', 'spurdo')
  258. })
  259. it('correctly normalizes favorite notifications', () => {
  260. const notif = makeMockNotificationQvitter({
  261. id: 123,
  262. ntype: 'like',
  263. notice: makeMockStatusQvitter({
  264. id: 444,
  265. favorited_status: makeMockStatusQvitter({ id: 4412 })
  266. }),
  267. is_seen: 1,
  268. from_profile: makeMockUserQvitter({ id: 'spurdo' })
  269. })
  270. expect(parseNotification(notif)).to.have.property('id', 123)
  271. expect(parseNotification(notif)).to.have.property('type', 'like')
  272. expect(parseNotification(notif)).to.have.property('seen', true)
  273. expect(parseNotification(notif)).to.have.nested.property('status.id', '4412')
  274. expect(parseNotification(notif)).to.have.nested.property('action.id', '444')
  275. expect(parseNotification(notif)).to.have.nested.property('from_profile.id', 'spurdo')
  276. })
  277. })
  278. describe('Link header pagination', () => {
  279. it('Parses min and max ids as integers', () => {
  280. const linkHeader = '<https://example.com/api/v1/notifications?max_id=861676>; rel="next", <https://example.com/api/v1/notifications?min_id=861741>; rel="prev"'
  281. const result = parseLinkHeaderPagination(linkHeader)
  282. expect(result).to.eql({
  283. maxId: 861676,
  284. minId: 861741
  285. })
  286. })
  287. it('Parses min and max ids as flakes', () => {
  288. const linkHeader = '<http://example.com/api/v1/timelines/home?max_id=9waQx5IIS48qVue2Ai>; rel="next", <http://example.com/api/v1/timelines/home?min_id=9wi61nIPnfn674xgie>; rel="prev"'
  289. const result = parseLinkHeaderPagination(linkHeader, { flakeId: true })
  290. expect(result).to.eql({
  291. maxId: '9waQx5IIS48qVue2Ai',
  292. minId: '9wi61nIPnfn674xgie'
  293. })
  294. })
  295. })
  296. })