diff --git a/models/authorization.js b/models/authorization.js index d55fcca20..417edbf49 100644 --- a/models/authorization.js +++ b/models/authorization.js @@ -118,6 +118,7 @@ function filterInput(user, feature, input, target) { title: input.title, body: input.body, status: input.status, + type: input.type, source_url: input.source_url, }; } diff --git a/models/balance.js b/models/balance.js index f04a0a45c..008ea8398 100644 --- a/models/balance.js +++ b/models/balance.js @@ -49,6 +49,9 @@ async function findAllByOriginatorId(originatorId, options) { async function create({ balanceType, recipientId, amount, originatorType, originatorId }, options = {}) { const tableName = tableNameMap[balanceType] || tableNameMap.default; const hasBalanceTypeColum = balanceType.startsWith('content:tabcoin'); + const totalBalanceFunction = sqlFunctionMap[balanceType] || sqlFunctionMap.default; + + const returning = options.withBalance ? `*, ${totalBalanceFunction}($1) as total` : '*'; const query = { text: ` @@ -56,7 +59,7 @@ async function create({ balanceType, recipientId, amount, originatorType, origin (recipient_id, amount, originator_type, originator_id${hasBalanceTypeColum ? ', balance_type' : ''}) VALUES ($1, $2, $3, $4${hasBalanceTypeColum ? ', $5' : ''}) - RETURNING * ; + RETURNING ${returning}; `, values: [recipientId, amount, originatorType, originatorId], }; diff --git a/models/content.js b/models/content.js index e3ebc8396..11f2ed632 100644 --- a/models/content.js +++ b/models/content.js @@ -1,7 +1,7 @@ import { randomUUID as uuidV4 } from 'node:crypto'; import slug from 'slug'; -import { ForbiddenError, ValidationError } from 'errors'; +import { ForbiddenError, UnprocessableEntityError, ValidationError } from 'errors'; import database from 'infra/database.js'; import balance from 'models/balance.js'; import pagination from 'models/pagination.js'; @@ -113,6 +113,7 @@ async function findAll(values = {}, options = {}) { contents.title, ${!values.attributes?.exclude?.includes('body') ? 'contents.body,' : ''} contents.status, + contents.type, contents.source_url, contents.created_at, contents.updated_at, @@ -286,6 +287,11 @@ async function create(postedContent, options = {}) { transaction: options.transaction, }); + await updateTabCashBalance(null, newContent, { + eventId: options.eventId, + transaction: options.transaction, + }); + const tabcoinsCount = await balance.getContentTabcoinsCreditDebit( { recipientId: newContent.id, @@ -317,8 +323,8 @@ async function create(postedContent, options = {}) { ), inserted_content as ( INSERT INTO - contents (id, parent_id, owner_id, slug, title, body, status, source_url, published_at, path) - SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, parent.child_path + contents (id, parent_id, owner_id, slug, title, body, status, source_url, published_at, type, path) + SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, parent.child_path FROM parent RETURNING * ) @@ -330,6 +336,7 @@ async function create(postedContent, options = {}) { inserted_content.title, inserted_content.body, inserted_content.status, + inserted_content.type, inserted_content.source_url, inserted_content.created_at, inserted_content.updated_at, @@ -355,6 +362,7 @@ async function create(postedContent, options = {}) { content.status, content.source_url, content.published_at, + content.type, ], }; @@ -440,6 +448,7 @@ function validateCreateSchema(content) { title: 'optional', body: 'required', status: 'required', + content_type: 'optional', source_url: 'optional', }); @@ -550,6 +559,11 @@ async function creditOrDebitTabCoins(oldContent, newContent, options = {}) { }); } + // We should not credit TabCoins to the user if the "type" is not "content". + if (newContent.type !== 'content') { + userEarnings = 0; + } + // We should not credit if the content has little or no value. if (newContent.body.split(/[a-z]{5,}/i, 6).length < 6) return; @@ -602,6 +616,36 @@ async function creditOrDebitTabCoins(oldContent, newContent, options = {}) { } } +async function updateTabCashBalance(oldContent, newContent, options = {}) { + if (newContent.type === 'content') { + return; + } + + const tabCashToDebitFromUser = -100; + + const balanceResult = await balance.create( + { + balanceType: 'user:tabcash', + recipientId: newContent.owner_id, + amount: tabCashToDebitFromUser, + originatorType: options.eventId ? 'event' : 'content', + originatorId: options.eventId ? options.eventId : newContent.id, + }, + { + transaction: options.transaction, + withBalance: true, + }, + ); + + if (balanceResult.total < 0) { + throw new UnprocessableEntityError({ + message: `Não foi possível criar a publicação.`, + action: `Você precisa de pelo menos ${Math.abs(tabCashToDebitFromUser)} TabCash para realizar esta ação.`, + errorLocationCode: 'MODEL:CONTENT:UPDATE_TABCASH:NOT_ENOUGH', + }); + } +} + async function update(contentId, postedContent, options = {}) { const validPostedContent = validateUpdateSchema(postedContent); @@ -674,6 +718,7 @@ async function update(contentId, postedContent, options = {}) { updated_content.title, updated_content.body, updated_content.status, + updated_content.type, updated_content.source_url, updated_content.created_at, updated_content.updated_at, diff --git a/models/validator.js b/models/validator.js index dc55e6a6e..81201207f 100644 --- a/models/validator.js +++ b/models/validator.js @@ -251,6 +251,16 @@ const schemas = { }); }, + content_type: function () { + return Joi.object({ + type: Joi.string() + .trim() + .valid('content', 'ad') + .default('content') + .when('$required.content_type', { is: 'required', then: Joi.required(), otherwise: Joi.optional() }), + }); + }, + source_url: function () { return Joi.object({ source_url: Joi.string() @@ -482,6 +492,7 @@ const schemas = { 'title', 'body', 'status', + 'content_type', 'source_url', 'created_at', 'updated_at', diff --git a/pages/api/v1/contents/index.public.js b/pages/api/v1/contents/index.public.js index a3a2166ec..24a856051 100644 --- a/pages/api/v1/contents/index.public.js +++ b/pages/api/v1/contents/index.public.js @@ -89,6 +89,7 @@ function postValidationHandler(request, response, next) { title: 'optional', body: 'required', status: 'optional', + content_type: 'optional', source_url: 'optional', }); diff --git a/tests/constants-for-tests.js b/tests/constants-for-tests.js index 731174eb1..484e9ec5f 100644 --- a/tests/constants-for-tests.js +++ b/tests/constants-for-tests.js @@ -1,2 +1,3 @@ +export const defaultTabCashForAdCreation = 100; export const maxSlugLength = 160; export const maxTitleLength = 255; diff --git a/tests/integration/api/v1/contents/[username]/[slug]/children/get.test.js b/tests/integration/api/v1/contents/[username]/[slug]/children/get.test.js index 311cd6e37..4914d374d 100644 --- a/tests/integration/api/v1/contents/[username]/[slug]/children/get.test.js +++ b/tests/integration/api/v1/contents/[username]/[slug]/children/get.test.js @@ -154,6 +154,7 @@ describe('GET /api/v1/contents/[username]/[slug]/children', () => { tabcoins_credit: 0, tabcoins_debit: 0, status: childBranchBLevel1.status, + type: 'content', source_url: childBranchBLevel1.source_url, created_at: childBranchBLevel1.created_at.toISOString(), updated_at: childBranchBLevel1.updated_at.toISOString(), @@ -172,6 +173,7 @@ describe('GET /api/v1/contents/[username]/[slug]/children', () => { tabcoins_credit: 0, tabcoins_debit: 0, status: childBranchBLevel2Content1.status, + type: 'content', source_url: childBranchBLevel2Content1.source_url, created_at: childBranchBLevel2Content1.created_at.toISOString(), updated_at: childBranchBLevel2Content1.updated_at.toISOString(), @@ -192,6 +194,7 @@ describe('GET /api/v1/contents/[username]/[slug]/children', () => { tabcoins_credit: 0, tabcoins_debit: 0, status: childBranchBLevel2Content2.status, + type: 'content', source_url: childBranchBLevel2Content2.source_url, created_at: childBranchBLevel2Content2.created_at.toISOString(), updated_at: childBranchBLevel2Content2.updated_at.toISOString(), @@ -212,6 +215,7 @@ describe('GET /api/v1/contents/[username]/[slug]/children', () => { title: childBranchALevel1.title, body: childBranchALevel1.body, status: childBranchALevel1.status, + type: 'content', tabcoins: 1, tabcoins_credit: 0, tabcoins_debit: 0, @@ -230,6 +234,7 @@ describe('GET /api/v1/contents/[username]/[slug]/children', () => { title: childBranchALevel2.title, body: childBranchALevel2.body, status: childBranchALevel2.status, + type: 'content', tabcoins: 1, tabcoins_credit: 0, tabcoins_debit: 0, @@ -251,6 +256,7 @@ describe('GET /api/v1/contents/[username]/[slug]/children', () => { tabcoins_credit: 0, tabcoins_debit: 0, status: childBranchALevel3.status, + type: 'content', source_url: childBranchALevel3.source_url, created_at: childBranchALevel3.created_at.toISOString(), updated_at: childBranchALevel3.updated_at.toISOString(), @@ -346,6 +352,7 @@ describe('GET /api/v1/contents/[username]/[slug]/children', () => { title: childBranchBLevel2Content2.title, body: childBranchBLevel2Content2.body, status: childBranchBLevel2Content2.status, + type: 'content', tabcoins: 1, tabcoins_credit: 0, tabcoins_debit: 0, @@ -366,6 +373,7 @@ describe('GET /api/v1/contents/[username]/[slug]/children', () => { title: childBranchBLevel2Content1.title, body: childBranchBLevel2Content1.body, status: childBranchBLevel2Content1.status, + type: 'content', tabcoins: 0, tabcoins_credit: 0, tabcoins_debit: 0, @@ -436,6 +444,7 @@ describe('GET /api/v1/contents/[username]/[slug]/children', () => { tabcoins_credit: 2, tabcoins_debit: -1, status: childBranchBLevel1.status, + type: 'content', source_url: childBranchBLevel1.source_url, created_at: childBranchBLevel1.created_at.toISOString(), updated_at: childBranchBLevel1.updated_at.toISOString(), @@ -453,6 +462,7 @@ describe('GET /api/v1/contents/[username]/[slug]/children', () => { title: childBranchALevel1.title, body: childBranchALevel1.body, status: childBranchALevel1.status, + type: 'content', tabcoins: 1, tabcoins_credit: 0, tabcoins_debit: 0, @@ -471,6 +481,7 @@ describe('GET /api/v1/contents/[username]/[slug]/children', () => { title: childBranchALevel2.title, body: childBranchALevel2.body, status: childBranchALevel2.status, + type: 'content', tabcoins: 5, tabcoins_credit: 4, tabcoins_debit: 0, diff --git a/tests/integration/api/v1/contents/[username]/[slug]/get.test.js b/tests/integration/api/v1/contents/[username]/[slug]/get.test.js index 7cadef0f4..768d8b772 100644 --- a/tests/integration/api/v1/contents/[username]/[slug]/get.test.js +++ b/tests/integration/api/v1/contents/[username]/[slug]/get.test.js @@ -96,6 +96,7 @@ describe('GET /api/v1/contents/[username]/[slug]', () => { title: 'Conteúdo publicamente disponível', body: 'Conteúdo relevante deveria estar disponível para todos.', status: 'published', + type: 'content', tabcoins: 1, tabcoins_credit: 0, tabcoins_debit: 0, @@ -203,6 +204,7 @@ describe('GET /api/v1/contents/[username]/[slug]', () => { title: 'Conteúdo root', body: 'Body with relevant texts needs to contain a good amount of words', status: 'published', + type: 'content', tabcoins: 1, tabcoins_credit: 0, tabcoins_debit: 0, @@ -285,6 +287,7 @@ describe('GET /api/v1/contents/[username]/[slug]', () => { title: null, body: 'Conteúdo child', status: 'published', + type: 'content', tabcoins: 0, tabcoins_credit: 0, tabcoins_debit: 0, @@ -355,6 +358,7 @@ describe('GET /api/v1/contents/[username]/[slug]', () => { title: null, body: 'Conteúdo child', status: 'published', + type: 'content', tabcoins: 0, tabcoins_credit: 0, tabcoins_debit: 0, @@ -441,6 +445,7 @@ describe('GET /api/v1/contents/[username]/[slug]', () => { title: 'Conteúdo com TabCoins', body: 'Conteúdo que foi avaliado positiva e negativamente.', status: 'published', + type: 'content', tabcoins: 2, tabcoins_credit: 3, tabcoins_debit: -1, diff --git a/tests/integration/api/v1/contents/[username]/[slug]/parent/get.test.js b/tests/integration/api/v1/contents/[username]/[slug]/parent/get.test.js index c3492d030..8f6f33ef7 100644 --- a/tests/integration/api/v1/contents/[username]/[slug]/parent/get.test.js +++ b/tests/integration/api/v1/contents/[username]/[slug]/parent/get.test.js @@ -224,6 +224,7 @@ describe('GET /api/v1/contents/[username]/[slug]/parent', () => { body: 'Root - Body with relevant texts needs to contain a good amount of words', children_deep_count: 1, status: 'published', + type: 'content', source_url: null, published_at: rootContent.published_at.toISOString(), created_at: rootContent.created_at.toISOString(), @@ -287,6 +288,7 @@ describe('GET /api/v1/contents/[username]/[slug]/parent', () => { body: 'Child content body Level 2 - relevant content', children_deep_count: 1, status: 'published', + type: 'content', source_url: null, published_at: childContentLevel2.published_at.toISOString(), created_at: childContentLevel2.created_at.toISOString(), @@ -354,6 +356,7 @@ describe('GET /api/v1/contents/[username]/[slug]/parent', () => { body: '[Não disponível]', children_deep_count: 0, status: 'draft', + type: 'content', source_url: null, published_at: null, created_at: childContentLevel2.created_at.toISOString(), @@ -422,6 +425,7 @@ describe('GET /api/v1/contents/[username]/[slug]/parent', () => { body: '[Não disponível]', children_deep_count: 0, status: 'deleted', + type: 'content', source_url: null, published_at: childContentLevel2.published_at.toISOString(), created_at: childContentLevel2.created_at.toISOString(), @@ -472,6 +476,7 @@ describe('GET /api/v1/contents/[username]/[slug]/parent', () => { body: 'Root - Body with relevant texts needs to contain a good amount of words', children_deep_count: 1, status: 'published', + type: 'content', source_url: null, published_at: rootContent.published_at.toISOString(), created_at: rootContent.created_at.toISOString(), diff --git a/tests/integration/api/v1/contents/[username]/[slug]/patch.test.js b/tests/integration/api/v1/contents/[username]/[slug]/patch.test.js index 0a379bf66..fa5a5619e 100644 --- a/tests/integration/api/v1/contents/[username]/[slug]/patch.test.js +++ b/tests/integration/api/v1/contents/[username]/[slug]/patch.test.js @@ -152,6 +152,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: null, body: 'Updated body, even without "create:content:text_root" feature.', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -198,6 +199,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Valid user trying to update "root" content.', body: 'It should be possible, even without the "create:content:text_child" feature.', status: 'published', + type: 'content', source_url: 'http://www.tabnews.com.br/', created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -453,6 +455,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Tentando atualizar o dono do conteúdo.', body: 'Campo "owner_id" da request deveria ser ignorado e pego através da sessão.', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -496,6 +499,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Título velho', body: 'Body novo', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -541,6 +545,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Title', body: 'New body', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -641,6 +646,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Começando com caractere proibido no Postgres', body: 'Terminando com caractere proibido no Postgres', status: 'draft', + type: 'content', source_url: 'https://teste-caractere.invalido/', created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -760,6 +766,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Título velho', body: 'Espaço só no fim', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -828,6 +835,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Título velho', body: 'Body velho', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -968,6 +976,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Segundo conteúdo', body: 'Segundo conteúdo', status: 'published', + type: 'content', tabcoins: 0, tabcoins_credit: 0, tabcoins_debit: 0, @@ -1042,6 +1051,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Título velho', body: 'Body velho', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1136,6 +1146,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Título velho', body: 'Body velho', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1178,6 +1189,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Título novo', body: 'Body velho', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1339,6 +1351,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: null, body: 'Child old body', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1381,6 +1394,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Título válido, mas com espaços em branco no início e no fim', body: 'Body velho', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1423,6 +1437,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: `Tab & News | Conteúdos com \n valor concreto e "massa"> participe! '\\o/'`, body: 'Body velho', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1465,6 +1480,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Título velho', body: 'Body velho', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1508,6 +1524,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Título velho', body: 'Body with relevant texts needs to contain a good amount of words', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1584,6 +1601,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Title', body: 'Body with relevant texts needs to contain a good amount of words', status: 'deleted', + type: 'content', tabcoins: 1, tabcoins_credit: 0, tabcoins_debit: 0, @@ -1775,6 +1793,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Título velho', body: 'Body velho', status: 'draft', + type: 'content', source_url: 'http://www.tabnews.com.br/', created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1817,6 +1836,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Título velho', body: 'Body velho', status: 'draft', + type: 'content', source_url: 'https://www.tabnews.com.br/museu', created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1859,6 +1879,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Alterar um baita de um Top-Level Domain', body: 'O maior TLD listado em http://data.iana.org/TLD/tlds-alpha-by-domain.txt possuía 24 caracteres', status: 'draft', + type: 'content', source_url: 'https://nic.xn--vermgensberatung-pwb/', created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1901,6 +1922,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Alterar URL bem curta', body: 'Por exemplo o encurtador do Telegram', status: 'draft', + type: 'content', source_url: 'https://t.me', created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -2078,6 +2100,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Título velho', body: 'Body velho', status: 'draft', + type: 'content', source_url: 'https://www.tabnews.com.br/api/v1/contents?strategy=old', created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -2121,6 +2144,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Título velho', body: 'Body velho', status: 'draft', + type: 'content', source_url: 'https://www.tabnews.com.br/#:~:text=TabNews,-Status', created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -2190,6 +2214,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Título velho', body: 'Body velho', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -2281,6 +2306,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Updated title, but not "parent_id"', body: 'Child content body', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -3176,6 +3202,7 @@ describe('PATCH /api/v1/contents/[username]/[slug]', () => { title: 'Novo title.', body: 'Novo body.', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, diff --git a/tests/integration/api/v1/contents/[username]/[slug]/root/get.test.js b/tests/integration/api/v1/contents/[username]/[slug]/root/get.test.js index 10aa3c700..12a828e41 100644 --- a/tests/integration/api/v1/contents/[username]/[slug]/root/get.test.js +++ b/tests/integration/api/v1/contents/[username]/[slug]/root/get.test.js @@ -224,6 +224,7 @@ describe('GET /api/v1/contents/[username]/[slug]/root', () => { body: 'Body with relevant texts needs to contain a good amount of words', children_deep_count: 1, status: 'published', + type: 'content', source_url: null, published_at: rootContent.published_at.toISOString(), created_at: rootContent.created_at.toISOString(), @@ -287,6 +288,7 @@ describe('GET /api/v1/contents/[username]/[slug]/root', () => { body: 'Body with relevant texts needs to contain a good amount of words', children_deep_count: 3, status: 'published', + type: 'content', source_url: null, published_at: rootContent.published_at.toISOString(), created_at: rootContent.created_at.toISOString(), @@ -355,6 +357,7 @@ describe('GET /api/v1/contents/[username]/[slug]/root', () => { body: 'Body with relevant texts needs to contain a good amount of words', children_deep_count: 2, status: 'published', + type: 'content', source_url: null, published_at: rootContent.published_at.toISOString(), created_at: rootContent.created_at.toISOString(), @@ -422,6 +425,7 @@ describe('GET /api/v1/contents/[username]/[slug]/root', () => { title: '[Não disponível]', body: '[Não disponível]', status: 'draft', + type: 'content', source_url: null, published_at: null, created_at: rootContent.created_at.toISOString(), @@ -491,6 +495,7 @@ describe('GET /api/v1/contents/[username]/[slug]/root', () => { title: '[Não disponível]', body: '[Não disponível]', status: 'deleted', + type: 'content', source_url: null, published_at: rootContent.published_at.toISOString(), created_at: rootContent.created_at.toISOString(), @@ -550,6 +555,7 @@ describe('GET /api/v1/contents/[username]/[slug]/root', () => { body: 'Body with relevant texts needs to contain a good amount of words', children_deep_count: 2, status: 'published', + type: 'content', source_url: null, published_at: rootContent.published_at.toISOString(), created_at: rootContent.created_at.toISOString(), diff --git a/tests/integration/api/v1/contents/[username]/get.test.js b/tests/integration/api/v1/contents/[username]/get.test.js index e4c0cf9be..ffb151b11 100644 --- a/tests/integration/api/v1/contents/[username]/get.test.js +++ b/tests/integration/api/v1/contents/[username]/get.test.js @@ -115,6 +115,7 @@ describe('GET /api/v1/contents/[username]', () => { title: null, body: 'Child content', status: 'published', + type: 'content', source_url: null, created_at: childContent.created_at.toISOString(), updated_at: childContent.updated_at.toISOString(), @@ -174,6 +175,7 @@ describe('GET /api/v1/contents/[username]', () => { title: null, body: 'Diferente do teste anterior, o corpo dessa publicação é grande, com quebras de linha, Markdown e ultrapassa o limite de caracteres que iremos devolver pelo response. Motivo Hoje estamos usando o mesmo número de caracteres de um title para que o fronten...', status: 'published', + type: 'content', source_url: null, created_at: childContent.created_at.toISOString(), updated_at: childContent.updated_at.toISOString(), @@ -242,6 +244,7 @@ describe('GET /api/v1/contents/[username]', () => { title: null, body: 'Este conteúdo agora deverá aparecer na lista retornada pelo /contents/[username]', status: 'published', + type: 'content', source_url: null, created_at: childContent.created_at.toISOString(), updated_at: childContent.updated_at.toISOString(), @@ -260,6 +263,7 @@ describe('GET /api/v1/contents/[username]', () => { slug: 'segundo-conteudo-criado', title: 'Segundo conteúdo criado', status: 'published', + type: 'content', source_url: null, created_at: secondRootContent.created_at.toISOString(), updated_at: secondRootContent.updated_at.toISOString(), @@ -278,6 +282,7 @@ describe('GET /api/v1/contents/[username]', () => { slug: 'primeiro-conteudo-criado', title: 'Primeiro conteúdo criado', status: 'published', + type: 'content', source_url: null, created_at: firstRootContent.created_at.toISOString(), updated_at: firstRootContent.updated_at.toISOString(), @@ -353,6 +358,7 @@ describe('GET /api/v1/contents/[username]', () => { title: 'Quarto conteúdo criado', body: 'Este conteúdo que agora deverá aparecer na lista retornada pelo /contents/[username]', status: 'published', + type: 'content', source_url: null, created_at: childContent.created_at.toISOString(), updated_at: childContent.updated_at.toISOString(), @@ -371,6 +377,7 @@ describe('GET /api/v1/contents/[username]', () => { slug: 'segundo-conteudo-criado', title: 'Segundo conteúdo criado', status: 'published', + type: 'content', source_url: null, created_at: secondRootContent.created_at.toISOString(), updated_at: secondRootContent.updated_at.toISOString(), @@ -389,6 +396,7 @@ describe('GET /api/v1/contents/[username]', () => { slug: 'primeiro-conteudo-criado', title: 'Primeiro conteúdo criado', status: 'published', + type: 'content', source_url: null, created_at: firstRootContent.created_at.toISOString(), updated_at: firstRootContent.updated_at.toISOString(), @@ -446,6 +454,7 @@ describe('GET /api/v1/contents/[username]', () => { title: 'Comentário', body: 'Um comentário', status: 'published', + type: 'content', source_url: null, created_at: childContent.created_at.toISOString(), updated_at: childContent.updated_at.toISOString(), @@ -464,6 +473,7 @@ describe('GET /api/v1/contents/[username]', () => { slug: 'conteudo-raiz', title: 'Conteúdo raiz', status: 'published', + type: 'content', source_url: null, created_at: rootContent.created_at.toISOString(), updated_at: rootContent.updated_at.toISOString(), @@ -823,6 +833,7 @@ describe('GET /api/v1/contents/[username]', () => { slug: 'segundo-conteudo-criado', title: 'Segundo conteúdo criado', status: 'published', + type: 'content', source_url: null, created_at: secondRootContent.created_at.toISOString(), updated_at: secondRootContent.updated_at.toISOString(), @@ -841,6 +852,7 @@ describe('GET /api/v1/contents/[username]', () => { slug: 'primeiro-conteudo-criado', title: 'Primeiro conteúdo criado', status: 'published', + type: 'content', source_url: null, created_at: firstRootContent.created_at.toISOString(), updated_at: firstRootContent.updated_at.toISOString(), @@ -936,6 +948,7 @@ describe('GET /api/v1/contents/[username]', () => { title: 'Quinto conteúdo criado', body: 'Este conteúdo deverá aparecer na lista retornada pelo /contents/[username]', status: 'published', + type: 'content', source_url: null, created_at: secondChildContent.created_at.toISOString(), updated_at: secondChildContent.updated_at.toISOString(), @@ -955,6 +968,7 @@ describe('GET /api/v1/contents/[username]', () => { title: 'Quarto conteúdo criado', body: 'Este conteúdo deverá aparecer na lista retornada pelo /contents/[username]', status: 'published', + type: 'content', source_url: null, created_at: firstChildContent.created_at.toISOString(), updated_at: firstChildContent.updated_at.toISOString(), diff --git a/tests/integration/api/v1/contents/get.test.js b/tests/integration/api/v1/contents/get.test.js index efab97a9e..681dc4468 100644 --- a/tests/integration/api/v1/contents/get.test.js +++ b/tests/integration/api/v1/contents/get.test.js @@ -149,6 +149,7 @@ describe('GET /api/v1/contents', () => { slug: 'segundo-conteudo-criado', title: 'Segundo conteúdo criado', status: 'published', + type: 'content', source_url: null, created_at: secondRootContent.created_at.toISOString(), updated_at: secondRootContent.updated_at.toISOString(), @@ -167,6 +168,7 @@ describe('GET /api/v1/contents', () => { slug: 'primeiro-conteudo-criado', title: 'Primeiro conteúdo criado', status: 'published', + type: 'content', source_url: null, created_at: firstRootContent.created_at.toISOString(), updated_at: firstRootContent.updated_at.toISOString(), @@ -245,6 +247,7 @@ describe('GET /api/v1/contents', () => { slug: 'primeiro-conteudo-criado', title: 'Primeiro conteúdo criado', status: 'published', + type: 'content', source_url: null, created_at: firstRootContent.created_at.toISOString(), updated_at: firstRootContent.updated_at.toISOString(), @@ -263,6 +266,7 @@ describe('GET /api/v1/contents', () => { slug: 'segundo-conteudo-criado', title: 'Segundo conteúdo criado', status: 'published', + type: 'content', source_url: null, created_at: secondRootContent.created_at.toISOString(), updated_at: secondRootContent.updated_at.toISOString(), @@ -334,6 +338,7 @@ describe('GET /api/v1/contents', () => { slug: 'conteudo-raiz', title: 'Conteúdo raiz', status: 'published', + type: 'content', source_url: null, created_at: rootContent.created_at.toISOString(), updated_at: rootContent.updated_at.toISOString(), @@ -1278,6 +1283,7 @@ describe('GET /api/v1/contents', () => { slug: 'primeiro-conteudo-criado', title: 'Primeiro conteúdo criado', status: 'published', + type: 'content', source_url: null, created_at: firstRootContent.created_at.toISOString(), updated_at: firstRootContent.updated_at.toISOString(), @@ -1304,6 +1310,7 @@ describe('GET /api/v1/contents', () => { slug: 'segundo-conteudo-criado', title: 'Segundo conteúdo criado', status: 'published', + type: 'content', source_url: null, created_at: secondRootContent.created_at.toISOString(), updated_at: secondRootContent.updated_at.toISOString(), @@ -1329,6 +1336,7 @@ describe('GET /api/v1/contents', () => { title: null, body: comment.body, status: 'published', + type: 'content', source_url: null, created_at: comment.created_at.toISOString(), updated_at: comment.updated_at.toISOString(), diff --git a/tests/integration/api/v1/contents/post.test.js b/tests/integration/api/v1/contents/post.test.js index 692d94ae7..e5a20cb43 100644 --- a/tests/integration/api/v1/contents/post.test.js +++ b/tests/integration/api/v1/contents/post.test.js @@ -1,7 +1,7 @@ import { version as uuidVersion } from 'uuid'; import database from 'infra/database'; -import { maxSlugLength, maxTitleLength } from 'tests/constants-for-tests'; +import { defaultTabCashForAdCreation, maxSlugLength, maxTitleLength } from 'tests/constants-for-tests'; import orchestrator from 'tests/orchestrator.js'; import RequestBuilder from 'tests/request-builder'; @@ -151,6 +151,7 @@ describe('POST /api/v1/contents', () => { title: 'Tentando criar conteúdo em nome de outro usuário', body: 'Campo "owner_id" da request deveria ser ignorado e pego através da sessão.', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -298,6 +299,7 @@ describe('POST /api/v1/contents', () => { title: 'Começando com caractere proibido no Postgres', body: 'Terminando com caractere proibido no Postgres', status: 'draft', + type: 'content', source_url: 'https://teste-caractere.invalido/', created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -390,6 +392,7 @@ describe('POST /api/v1/contents', () => { title: 'Título normal', body: 'Espaço só no fim', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -445,6 +448,7 @@ describe('POST /api/v1/contents', () => { title: 'Mini curso de Node.js', body: 'Instale o Node.js', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -507,6 +511,7 @@ describe('POST /api/v1/contents', () => { title: 'Mini curso de Node.js', body: 'Instale o Node.js', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -663,6 +668,7 @@ describe('POST /api/v1/contents', () => { title: 'Conteúdo existente', body: 'Outro body', status: 'published', + type: 'content', tabcoins: 0, tabcoins_credit: 0, tabcoins_debit: 0, @@ -699,6 +705,7 @@ describe('POST /api/v1/contents', () => { title: 'Mini curso de Node.js', body: 'Instale o Node.js', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -783,6 +790,7 @@ describe('POST /api/v1/contents', () => { ), body: 'Instale o Node.js', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -818,6 +826,7 @@ describe('POST /api/v1/contents', () => { title: 'Braille Pattern Blank Unicode Character', body: 'Instale o Node.js', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -853,6 +862,7 @@ describe('POST /api/v1/contents', () => { title: '♥'.repeat(maxTitleLength), body: `The title is ${maxTitleLength} characters but 765 bytes and the slug should only be ${maxSlugLength} bytes`, status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -888,6 +898,7 @@ describe('POST /api/v1/contents', () => { title: 'Título válido, mas com espaços em branco no início e no fim', body: 'Qualquer coisa.', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -923,6 +934,7 @@ describe('POST /api/v1/contents', () => { title: `Tab & News | Conteúdos com \n valor concreto e "massa"> participe! '\\o/'`, body: 'Qualquer coisa.', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -959,6 +971,7 @@ describe('POST /api/v1/contents', () => { title: 'Deveria criar um conteúdo com status "draft".', body: 'Qualquer coisa.', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -995,6 +1008,7 @@ describe('POST /api/v1/contents', () => { title: 'Deveria criar um conteúdo com status "published".', body: 'E isso vai fazer ter um "published_at" preenchido automaticamente.', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1146,6 +1160,7 @@ describe('POST /api/v1/contents', () => { title: 'TabNews', body: 'Somos pessoas brutalmente exatas e empáticas, simultaneamente.', status: 'draft', + type: 'content', source_url: 'http://www.tabnews.com.br/', created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1182,6 +1197,7 @@ describe('POST /api/v1/contents', () => { title: 'TabNews: Onde Tudo Começou', body: 'Aqui você vai encontrar POCs que foram criadas pela turma no início do projeto.', status: 'draft', + type: 'content', source_url: 'https://www.tabnews.com.br/museu', created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1218,6 +1234,7 @@ describe('POST /api/v1/contents', () => { title: 'Um baita de um Top-Level Domain', body: 'O maior TLD listado em http://data.iana.org/TLD/tlds-alpha-by-domain.txt possuía 24 caracteres', status: 'draft', + type: 'content', source_url: 'http://nic.xn--vermgensberatung-pwb/', created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1254,6 +1271,7 @@ describe('POST /api/v1/contents', () => { title: 'URL bem curta', body: 'Por exemplo o encurtador do Telegram', status: 'draft', + type: 'content', source_url: 'https://t.me', created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1400,6 +1418,7 @@ describe('POST /api/v1/contents', () => { title: 'Titulo', body: 'Corpo', status: 'draft', + type: 'content', source_url: 'https://www.tabnews.com.br/api/v1/contents?strategy=old', created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1436,6 +1455,7 @@ describe('POST /api/v1/contents', () => { title: 'Titulo', body: 'Corpo', status: 'draft', + type: 'content', source_url: 'http://www.tabnews.com.br/#:~:text=TabNews,-Status', created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1492,6 +1512,7 @@ describe('POST /api/v1/contents', () => { title: 'Titulo', body: 'Corpo', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1529,6 +1550,7 @@ describe('POST /api/v1/contents', () => { 'Deveria conseguir! E o campo "slug" é opcional & 95,5% dos usuários não usam :) [áéíóú?@#$*<>|+-=.,;:_] <- (caracteres especiais)', body: 'Deveria conseguir, pois atualmente todos os usuários recebem todas as features relacionadas a "content".', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1571,6 +1593,7 @@ describe('POST /api/v1/contents', () => { title: 'under_score 5% é >= 1 e <= 10 email@dominio.com #item1,item2 a&b | a & b/mil', body: 'Body', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1649,6 +1672,7 @@ describe('POST /api/v1/contents', () => { title: null, body: 'Deveria conseguir, pois atualmente todos os usuários recebem todas as features relacionadas a "content".', status: 'draft', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1700,6 +1724,7 @@ describe('POST /api/v1/contents', () => { title: null, body: 'Deveria criar um slug com UUID V4', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -1745,6 +1770,7 @@ describe('POST /api/v1/contents', () => { title: 'Título em um child content! O que vai acontecer?', body: 'Deveria criar um slug baseado no "title"', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -2421,6 +2447,7 @@ describe('POST /api/v1/contents', () => { title: 'Title', body: 'Body with relevant texts needs to contain a good amount of words', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -2472,6 +2499,7 @@ describe('POST /api/v1/contents', () => { title: null, body: 'Deveria conseguir mesmo com alguns comentários mal avaliados.', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -2515,6 +2543,7 @@ describe('POST /api/v1/contents', () => { title: 'Title', body: 'Body with relevant texts needs to contain a good amount of words', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -2566,6 +2595,7 @@ describe('POST /api/v1/contents', () => { title: null, body: 'Deve conseguir publicar, mas não deve ganhar TabCoins sem prestígio suficiente.', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -2609,6 +2639,7 @@ describe('POST /api/v1/contents', () => { title: 'Title', body: 'Body with relevant texts needs to contain a good amount of words', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -2660,6 +2691,7 @@ describe('POST /api/v1/contents', () => { title: null, body: 'Deve conseguir publicar e ganhar TabCoins com esse texto.', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -2732,6 +2764,7 @@ describe('POST /api/v1/contents', () => { title: 'Title', body: 'Body with no minimum amount of relevant words', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -2783,6 +2816,7 @@ describe('POST /api/v1/contents', () => { title: null, body: 'Body with no minimum amount of relevant words', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -2828,6 +2862,7 @@ describe('POST /api/v1/contents', () => { title: 'Title', body: 'Relevant text needs to contain a good amount of words', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -2879,6 +2914,7 @@ describe('POST /api/v1/contents', () => { title: null, body: 'Relevant text needs to contain a good amount of words', status: 'published', + type: 'content', source_url: null, created_at: responseBody.created_at, updated_at: responseBody.updated_at, @@ -2900,5 +2936,224 @@ describe('POST /api/v1/contents', () => { expect(userResponseBody.tabcash).toEqual(0); }); }); + + describe('With "type: ad"', () => { + test('Should be able to create "ad" type content', async () => { + const contentsRequestBuilder = new RequestBuilder('/api/v1/contents'); + const defaultUser = await contentsRequestBuilder.buildUser(); + + orchestrator.createBalance({ + balanceType: 'user:tabcash', + recipientId: defaultUser.id, + amount: defaultTabCashForAdCreation, + }); + + const { response, responseBody } = await contentsRequestBuilder.post({ + title: 'Ad title', + body: 'Ad body', + status: 'published', + type: 'ad', + source_url: 'https://www.tabnews.com.br', + }); + + expect.soft(response.status).toBe(201); + + expect(responseBody).toStrictEqual({ + id: responseBody.id, + owner_id: defaultUser.id, + parent_id: null, + slug: 'ad-title', + title: 'Ad title', + body: 'Ad body', + status: 'published', + type: 'ad', + source_url: 'https://www.tabnews.com.br', + created_at: responseBody.created_at, + updated_at: responseBody.updated_at, + published_at: responseBody.published_at, + deleted_at: null, + tabcoins: 0, + tabcoins_credit: 0, + tabcoins_debit: 0, + owner_username: defaultUser.username, + }); + + expect(uuidVersion(responseBody.id)).toBe(4); + expect(Date.parse(responseBody.created_at)).not.toBeNaN(); + expect(Date.parse(responseBody.updated_at)).not.toBeNaN(); + }); + + test(`Should debit ${defaultTabCashForAdCreation} TabCash`, async () => { + const contentsRequestBuilder = new RequestBuilder('/api/v1/contents'); + const usersRequestBuilder = new RequestBuilder('/api/v1/users'); + const defaultUser = await contentsRequestBuilder.buildUser(); + + orchestrator.createBalance({ + balanceType: 'user:tabcash', + recipientId: defaultUser.id, + amount: 1_000 + defaultTabCashForAdCreation, + }); + + const { response: contentResponse } = await contentsRequestBuilder.post({ + title: 'Ad title', + body: 'Ad body', + status: 'published', + type: 'ad', + }); + + const { responseBody: userResponseBody } = await usersRequestBuilder.get(`/${defaultUser.username}`); + + expect.soft(contentResponse.status).toBe(201); + expect(userResponseBody.tabcash).toBe(1_000); + }); + + test(`Should not be able to create without enough TabCash`, async () => { + const contentsRequestBuilder = new RequestBuilder('/api/v1/contents'); + const usersRequestBuilder = new RequestBuilder('/api/v1/users'); + const defaultUser = await contentsRequestBuilder.buildUser(); + + orchestrator.createBalance({ + balanceType: 'user:tabcash', + recipientId: defaultUser.id, + amount: defaultTabCashForAdCreation - 1, + }); + + const { response, responseBody } = await contentsRequestBuilder.post({ + title: 'Ad title', + body: 'Ad body', + status: 'published', + type: 'ad', + }); + + expect.soft(response.status).toBe(422); + + expect(responseBody).toStrictEqual({ + name: 'UnprocessableEntityError', + message: 'Não foi possível criar a publicação.', + action: `Você precisa de pelo menos ${defaultTabCashForAdCreation} TabCash para realizar esta ação.`, + status_code: 422, + error_id: responseBody.error_id, + request_id: responseBody.request_id, + error_location_code: 'MODEL:CONTENT:UPDATE_TABCASH:NOT_ENOUGH', + }); + + const { responseBody: userResponseBody } = await usersRequestBuilder.get(`/${defaultUser.username}`); + + expect(userResponseBody.tabcash).toBe(defaultTabCashForAdCreation - 1); + }); + + test('Should not be able to create with "parent_id"', async () => { + const contentsRequestBuilder = new RequestBuilder('/api/v1/contents'); + const defaultUser = await contentsRequestBuilder.buildUser(); + + const { responseBody: rootContent } = await contentsRequestBuilder.post({ + title: 'Root title', + body: 'Root body', + status: 'published', + }); + + orchestrator.createBalance({ + balanceType: 'user:tabcash', + recipientId: defaultUser.id, + amount: defaultTabCashForAdCreation, + }); + + const { response, responseBody } = await contentsRequestBuilder.post({ + title: 'Ad title', + body: 'Ad body', + status: 'published', + type: 'ad', + parent_id: rootContent.id, + }); + + expect.soft(response.status).toBe(201); + + expect(responseBody).toStrictEqual({ + id: responseBody.id, + owner_id: defaultUser.id, + parent_id: rootContent.id, + slug: 'ad-title', + title: 'Ad title', + body: 'Ad body', + status: 'published', + type: 'content', + source_url: null, + created_at: responseBody.created_at, + updated_at: responseBody.updated_at, + published_at: responseBody.published_at, + deleted_at: null, + tabcoins: 0, + tabcoins_credit: 0, + tabcoins_debit: 0, + owner_username: defaultUser.username, + }); + + expect(uuidVersion(responseBody.id)).toBe(4); + expect(Date.parse(responseBody.created_at)).not.toBeNaN(); + expect(Date.parse(responseBody.updated_at)).not.toBeNaN(); + }); + + test('Should not credit TabCoins to the user', async () => { + const contentsRequestBuilder = new RequestBuilder('/api/v1/contents'); + const usersRequestBuilder = new RequestBuilder('/api/v1/users'); + const defaultUser = await contentsRequestBuilder.buildUser(); + await orchestrator.createPrestige(defaultUser.id, { rootPrestigeNumerator: 2, rootPrestigeDenominator: 10 }); + + orchestrator.createBalance({ + balanceType: 'user:tabcash', + recipientId: defaultUser.id, + amount: defaultTabCashForAdCreation, + }); + + const { response: contentResponse, responseBody: contentResponseBody } = await contentsRequestBuilder.post({ + title: 'Title', + body: 'Relevant text needs to contain a good amount of words', + status: 'published', + type: 'ad', + }); + + const { responseBody: userResponseBody } = await usersRequestBuilder.get(`/${defaultUser.username}`); + + expect.soft(contentResponse.status).toBe(201); + expect(contentResponseBody.tabcoins).toBe(1); + expect(contentResponseBody.type).toBe('ad'); + expect(userResponseBody.tabcoins).toBe(0); + expect(userResponseBody.tabcash).toBe(0); + }); + }); + + describe('With invalid "type"', () => { + test('Should not be able to POST with invalid "type"', async () => { + const contentsRequestBuilder = new RequestBuilder('/api/v1/contents'); + const defaultUser = await contentsRequestBuilder.buildUser(); + + orchestrator.createBalance({ + balanceType: 'user:tabcash', + recipientId: defaultUser.id, + amount: defaultTabCashForAdCreation, + }); + + const { response, responseBody } = await contentsRequestBuilder.post({ + title: 'Ad title', + body: 'Ad body', + status: 'published', + type: 'invalid_type', + }); + + expect.soft(response.status).toBe(400); + + expect(responseBody).toStrictEqual({ + name: 'ValidationError', + message: '"type" deve possuir um dos seguintes valores: "content", "ad".', + action: 'Ajuste os dados enviados e tente novamente.', + status_code: 400, + error_id: responseBody.error_id, + request_id: responseBody.request_id, + error_location_code: 'MODEL:VALIDATOR:FINAL_SCHEMA', + key: 'type', + type: 'any.only', + }); + }); + }); }); }); diff --git a/tests/orchestrator.js b/tests/orchestrator.js index c880c380e..d1c62eea5 100644 --- a/tests/orchestrator.js +++ b/tests/orchestrator.js @@ -216,6 +216,7 @@ async function createContent(contentObject) { slug: contentObject?.slug || undefined, body: contentObject?.body || faker.lorem.paragraphs(5), status: contentObject?.status || 'draft', + type: contentObject?.type || 'content', source_url: contentObject?.source_url || undefined, }, {