Como resolver?

O ponto chave nesse desafio é a manipulação de strings. Como iremos precisar fazer verificações bem específicas podemos utilizar expressões regulares, ou simplesmente regex, pois elas nos permitem aplicar regras mais complexas na hora de manipular strings.

Resolução

Uma forma simples de começar a resolver esse desafio é fazendo uma validação por vez e utilizar returns para encerrar a função a cada etapa. Podemos começar pela validação de tamanho, para ela o String.length deve ser suficiente. Caso não seja válido a função irá retornar false e se encerrar, caso seja ela seguirá para retornar true.

function validateUser(username) {  if (username.length < 4 || username.length > 32) {
    return false
  }

  return true
}

A próxima validação é que a string contenha apenas letras, números ou o _. Poderíamos encontrar formas alternativas de fazer essa verificação, mas uma simples que podemos aproveitar são as RegEx. Uma expressão regular é um expressão escrita em uma linguagem própria que serve para identificar, agrupar e realizar outros procedimentos em strings. Ela possui recursos bem avançados e específicos para lidar com strings, e um deles é exatamente identificar os seus caracteres. Vamos utilizar uma RegEx para tentar encontrar caracteres que não sejam esse citados e, caso encontre-os, retorne false. O \W é utilizado exatamente para essa funcionalidade.

function validateUser(username) {
  if (username.length < 4 || username.length > 32) {
    return false
  }

  if (username.match(/\\W/)) {
    return false
  }

  return true
}

Agora precisamos verificar se a string começa com um número ou com um _ e então retornar false. Para isso também podemos utilizar RegEx, mas dessa vez com alguns recursos a mais. O ^ serve para indicar o começo da string, então usamos para mostrar que queremos pesquisar a partir do começo. Os colchetes são um conjunto, ou seja, ele permite pesquisar por vários caracteres de uma só vez. Já o | serve como um operador OR, permitindo que ele cheque por uma expressão ou outra.

function validateUser(username) {
  if (username.length < 4 || username.length > 32) {
    return false
  }

  if (username.match(/\\W/)) {
    return false
  }

  if (username.match(/^[0-9]|^_/)) {
    return false
  }

  return true
}

Agora precisamos verificar se a string não termina com _ e mais umas vez podemos utilizar RegEx. Dessa vez vamos utilizar o oposto do ^, o $. O $ serve para marcar o final da string, então conseguimos indicar que estamos pesquisando junto ao fim dela.

function validateUser(username) {
  if (username.length < 4 || username.length > 32) {
    return false
  }

  if (username.match(/\\W/)) {
    return false
  }

  if (username.match(/^[0-9]|^_/)) {
    return false
  }

  if (username.match(/_$/)) {
    return false
  }

  return true
}

Agora temos uma verificação um pouco maior, que é a de pelo menos um tipo de cada caractere presente. Ainda podemos utilizar RegEx, mas dessa vez com a negação de uma verificação mais longa. Vamos verificar a negação da string possuir letras E números E _, portanto se ela não possuir qualquer um destes deverá retornar false.

function validateUser(username) {
  if (username.length < 4 || username.length > 32) {
    return false
  }

  if (username.match(/\\W/)) {
    return false
  }

  if (username.match(/^[0-9]|^_/)) {
    return false
  }

  if (username.match(/_$/)) {
    return false
  }

  if (! (username.match(/[A-Za-z]/) && username.match(/[0-9]/) && username.match(/_/))) {
    return false
  }

  return true
}

Por último, só precisamos verificar se o nome de usuário é único. Para simular um banco de dados armazenando os nomes podemos utilizar um array simples externo à nossa função. Com isso podemos tentar encontrar o nome de usuário correspondente nesse array. No caso de um array podemos pesquisar através do método .indexOf(), que retorna um índice caso seja encontrado, mas retorna -1 caso não encontre. Se este nome existir, então só precisamos retornar false. E caso nenhuma validação falhe ele irá retornar true no fim de tudo.

const database = ['erick_14', 'pam_ls2', 'VICTOR_99A']

function validateUser(username) {
  if (username.length < 4 || username.length > 32) {
    return false
  }

  if (username.match(/\\W/)) {
    return false
  }

  if (username.match(/^[0-9]|^_/)) {
    return false
  }

  if (username.match(/_$/)) {
    return false
  }

  if (! (username.match(/[A-Za-z]/) && username.match(/[0-9]/) && username.match(/_/))) {
    return false
  }

  const usernameAlreadyExists = database.indexOf(username) !== -1

  if (usernameAlreadyExists) {
    return false
  }

  return true
}

Refatorando

Como vimos no decorrer da resolução, as expressões regulares podem ser verificadas simultaneamente utilizando o | que funciona como o operador OR. Portanto, podemos reduzir o nosso código ao unir múltiplas verificações semelhantes em apenas uma, porque se qualquer uma delas falhar (OR) a função de retornar false. O nosso código ficará da seguinte forma:

const database = ['erick_14', 'pam_ls2', 'VICTOR_99A']

function validateUser(username) {
  if (username.length < 4 || username.length > 32) {
    return false
  }

  if (username.match(/\\W|^[0-9]|^_|_$/)) {
    return false
  }

  if (! (username.match(/[A-Za-z]/) && username.match(/[0-9]/) && username.match(/_/))) {
    return false
  }

  const usernameAlreadyExists = database.indexOf(username) !== -1

  if (usernameAlreadyExists) {
    return false
  }

  return true
}