Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions scripts/yodev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Description:
// Busca ofertas de trabajo en yodev.dev - plataforma de empleos para desarrolladores
//
// Dependencies:
// - process.env: Access to environment variables
//
// Configuration:
// YODEV_API_URL - URL del endpoint de InnovaJobs
//
// Commands:
// hubot yodev <query> - Busca ofertas de trabajo relacionadas con el query
//
// Examples:
// hubot yodev javascript - Busca trabajos de JavaScript
//
// Author:
// @jorgeepunan

const https = require('https')

// Configuración desde variables de entorno
const YODEV_API_URL = process.env.YODEV_API_URL

module.exports = (robot) => {
robot.respond(/yodev\s+(.+)/i, (res) => {
const query = res.match[1]
const url = `${YODEV_API_URL}?query=${encodeURIComponent(query)}&country=chile`

const isSlack = robot.adapter && robot.adapter.constructor && robot.adapter.constructor.name === 'SlackBot'
const send = (text) => {
if (isSlack && robot.adapter.client && robot.adapter.client.web) {
const options = { unfurl_links: false, unfurl_media: false, as_user: true }
return robot.adapter.client.web.chat.postMessage(res.message.room, text, options)
}

return res.send(text)
}

send(`🔎 Buscando *${query}*... explorando oportunidades en yodev.dev 🚀 💼`)

https.get(url, (response) => {
let data = ''

response.on('data', (chunk) => {
data += chunk
})

response.on('end', () => {
try {
const responseData = JSON.parse(data)
const jobs = Array.isArray(responseData.jobs) ? responseData.jobs : []
const totalJobs = responseData.total_jobs || jobs.length
const viewMoreUrl = responseData.view_more_url

if (jobs.length === 0) {
const noResults = [
'🤖💥 404: yodev not found. Intenta con otro query antes de que tu portfolio se quede sin commits.',
'😅🛠️ Nada por aquí… parece que yodev está en modo mantenimiento. Prueba otro término.',
'🥲⌨️ Cero resultados. Ni un "Hello World" de trabajo apareció. Cambia el query y reintenta.',
'😭🐛 Encontré exactamente 0 ofertas. Es como buscar bugs en producción: siempre aparecen… excepto hoy.'
]
return send(noResults[Math.floor(Math.random() * noResults.length)])
}

const limitedJobs = jobs.slice(0, 5)
const messages = limitedJobs.map((job) => {
const lines = [`
*${job.title}*`,
`🏢 *Empresa:* ${job.company}`,
`📍 *Ubicación:* ${job.location}`,
`🔗 *Postulación:* ${job.url}`,
`📅 *Publicado:* ${job.posted || job.date || ''}`
]

if (job.salary) lines.push(`💰 *Sueldo:* ${job.salary}`)
if (job.type) lines.push(`🧩 *Tipo:* ${job.type}`)
if (job.source) lines.push(`🛰️ *Fuente:* ${job.source}`)

return lines.join('\n')
})

if (viewMoreUrl) {
messages.push(`\n🔍 ¿Quieres ver más ofertas? Visita: ${viewMoreUrl}`)
}

send(`✅ Encontré ${totalJobs} resultado(s) en yodev.dev:\n\n${messages.join('\n\n---\n\n')}`)
} catch (error) {
robot.logger.error(`Error al parsear la respuesta: ${error.message}`)
send('Ups! Ocurrió un error al procesar las ofertas de trabajo de yodev.dev. 😱')
}
})
}).on('error', (error) => {
robot.logger.error(`Error al buscar ofertas de trabajo: ${error.message}`)
send('Ups! Ocurrió un error al buscar ofertas de trabajo en yodev.dev. 😱')
})
})
}
95 changes: 95 additions & 0 deletions test/yodev.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import 'coffee-script/register'
import test from 'ava'
import Helper from 'hubot-test-helper'
import nock from 'nock'

const helper = new Helper('../scripts/yodev.js')
const sleep = m => new Promise(resolve => setTimeout(() => resolve(), m))

test.beforeEach(t => {
t.context.room = helper.createRoom({ httpd: false })
})

test.afterEach(t => t.context.room.destroy())

test('yodev debe retornar resultados con una query válida', async t => {
nock('https://yodev.dev')
.get('/api/jobs')
.query({ query: 'javascript', country: 'chile' })
.reply(200, {
jobs: [
{
title: 'Desarrollador JavaScript',
company: 'Tech Company',
location: 'Santiago, Chile',
url: 'https://example.com/job1',
posted: '2025-12-14',
salary: '$2.000.000 - $3.000.000',
type: 'Full-time',
source: 'LinkedIn'
},
{
title: 'Frontend Developer',
company: 'Another Company',
location: 'Valparaíso, Chile',
url: 'https://example.com/job2',
posted: '2025-12-13'
}
],
total_jobs: 2,
view_more_url: 'https://yodev.dev/search?q=javascript'
})

t.context.room.user.say('user', 'hubot yodev javascript')
await sleep(500)

const user = t.context.room.messages[0]
const hubotMessage1 = t.context.room.messages[1]
const hubotMessage2 = t.context.room.messages[2]

t.deepEqual(user, ['user', 'hubot yodev javascript'])
t.deepEqual(hubotMessage1, ['hubot', '🔎 Buscando *javascript*... explorando oportunidades en yodev.dev 🚀 💼'])
t.true(hubotMessage2[1].includes('✅ Encontré 2 resultado(s) en yodev.dev:'))
t.true(hubotMessage2[1].includes('Desarrollador JavaScript'))
t.true(hubotMessage2[1].includes('Frontend Developer'))
t.true(hubotMessage2[1].includes('🔍 ¿Quieres ver más ofertas?'))
})

test('yodev debe retornar mensaje cuando no hay resultados', async t => {
nock('https://yodev.dev')
.get('/api/jobs')
.query({ query: 'cobol', country: 'chile' })
.reply(200, {
jobs: [],
total_jobs: 0
})

t.context.room.user.say('user', 'hubot yodev cobol')
await sleep(500)

const user = t.context.room.messages[0]
const hubotMessage1 = t.context.room.messages[1]
const hubotMessage2 = t.context.room.messages[2]

t.deepEqual(user, ['user', 'hubot yodev cobol'])
t.deepEqual(hubotMessage1, ['hubot', '🔎 Buscando *cobol*... explorando oportunidades en yodev.dev 🚀 💼'])
t.true(hubotMessage2[1].includes('404') || hubotMessage2[1].includes('Cero resultados') || hubotMessage2[1].includes('Nada por aquí') || hubotMessage2[1].includes('Encontré exactamente 0'))
})

test('yodev debe manejar errores de la API', async t => {
nock('https://yodev.dev')
.get('/api/jobs')
.query({ query: 'python', country: 'chile' })
.reply(500, 'Internal Server Error')

t.context.room.user.say('user', 'hubot yodev python')
await sleep(500)

const user = t.context.room.messages[0]
const hubotMessage1 = t.context.room.messages[1]
const hubotMessage2 = t.context.room.messages[2]

t.deepEqual(user, ['user', 'hubot yodev python'])
t.deepEqual(hubotMessage1, ['hubot', '🔎 Buscando *python*... explorando oportunidades en yodev.dev 🚀 💼'])
t.deepEqual(hubotMessage2, ['hubot', 'Ups! Ocurrió un error al procesar las ofertas de trabajo de yodev.dev. 😱'])
})
Loading