eletrotupi / tcc / api/tests/database-auth.test.ts master
7.0 KB Raw
import test from "ava";
import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import bcrypt from 'bcryptjs';
import 'dotenv/config';

let prisma: PrismaClient;

test.before(() => {
  process.env.NODE_ENV = 'test';

  const connectionString = process.env.DATABASE_URL || 'postgres://user:password@orbit_db:5432/orbit_test';
  const adapter = new PrismaPg({ connectionString });
  prisma = new PrismaClient({ adapter });
});

test.beforeEach(async () => {
  // Clean database before each test
  try {
    await prisma.user.deleteMany({});
  } catch (err) {
    console.error('Error cleaning test DB:', err);
  }
});

test.after.always(async () => {
  const deleteUsers = prisma.user.deleteMany();

  await prisma.$transaction([
    deleteUsers
  ])

  await prisma?.$disconnect();
});

// Basic database connectivity test
test("database connection works", async (t) => {
  const result = await prisma.$queryRaw`SELECT 1 as test`;
  t.truthy(result);
});

// User creation and authentication tests
test("can create user with encrypted password", async (t) => {
  const encryptedPassword = bcrypt.hashSync("testPassword123", 10);

  const user = await prisma.user.create({
    data: {
      email: "test@example.com",
      firstName: "Test",
      lastName: "User",
      encryptedPassword: encryptedPassword
    }
  });

  t.is(user.email, "test@example.com");
  t.is(user.firstName, "Test");
  t.truthy(user.id);

  // Verify password can be validated
  t.true(bcrypt.compareSync("testPassword123", user.encryptedPassword));
  t.false(bcrypt.compareSync("wrongPassword", user.encryptedPassword));
});

test("can find user by email for authentication", async (t) => {
  // Create user
  await prisma.user.create({
    data: {
      email: "login@example.com",
      firstName: "Login",
      lastName: "User",
      encryptedPassword: bcrypt.hashSync("password123", 10)
    }
  });

  // Simulate login lookup
  const foundUser = await prisma.user.findUnique({
    where: { email: "login@example.com" }
  });

  t.truthy(foundUser);
  t.is(foundUser!.email, "login@example.com");
  t.true(bcrypt.compareSync("password123", foundUser!.encryptedPassword));
  t.false(bcrypt.compareSync("wrongPassword", foundUser!.encryptedPassword));
});

test("email uniqueness constraint prevents duplicate registrations", async (t) => {
  const email = "duplicate@example.com";

  // Create first user
  await prisma.user.create({
    data: {
      email,
      firstName: "First",
      lastName: "User",
      encryptedPassword: bcrypt.hashSync("password", 10)
    }
  });

  // Try to create second user with same email - should fail
  await t.throwsAsync(
    async () => {
      await prisma.user.create({
        data: {
          email,
          firstName: "Second",
          lastName: "User",
          encryptedPassword: bcrypt.hashSync("password", 10)
        }
      });
    }
  );
});

test("password reset flow works with database", async (t) => {
  const resetToken = "reset-token-123";
  const resetExpires = new Date(Date.now() + 3600000); // 1 hour from now

  // Create user
  const user = await prisma.user.create({
    data: {
      email: "reset@example.com",
      firstName: "Reset",
      lastName: "User",
      encryptedPassword: bcrypt.hashSync("oldPassword", 10)
    }
  });

  // Step 1: Set reset token (forgot password)
  await prisma.user.update({
    where: { id: user.id },
    data: {
      passwordResetToken: resetToken,
      passwordResetExpires: resetExpires
    }
  });

  // Step 2: Find user by reset token
  const userWithToken = await prisma.user.findFirst({
    where: {
      passwordResetToken: resetToken,
      passwordResetExpires: { gte: new Date() } // Token not expired
    }
  });

  t.truthy(userWithToken);
  t.is(userWithToken!.email, "reset@example.com");

  // Step 3: Reset password
  const newPassword = bcrypt.hashSync("newPassword123", 10);
  const updatedUser = await prisma.user.update({
    where: { id: user.id },
    data: {
      encryptedPassword: newPassword,
      passwordResetToken: null,
      passwordResetExpires: null
    }
  });

  // Verify new password works and reset fields are cleared
  t.true(bcrypt.compareSync("newPassword123", updatedUser.encryptedPassword));
  t.false(bcrypt.compareSync("oldPassword", updatedUser.encryptedPassword));
  t.is(updatedUser.passwordResetToken, null);
  t.is(updatedUser.passwordResetExpires, null);
});

test("expired reset tokens are not found", async (t) => {
  const expiredResetToken = "expired-token";
  const pastDate = new Date(Date.now() - 3600000); // 1 hour ago

  // Create user with expired token
  const user = await prisma.user.create({
    data: {
      email: "expired@example.com",
      firstName: "Expired",
      lastName: "User",
      encryptedPassword: bcrypt.hashSync("password", 10),
      passwordResetToken: expiredResetToken,
      passwordResetExpires: pastDate
    }
  });

  // Try to find user by expired token
  const foundUser = await prisma.user.findFirst({
    where: {
      passwordResetToken: expiredResetToken,
      passwordResetExpires: { gte: new Date() } // Token not expired
    }
  });

  // Should not find the user because token is expired
  t.is(foundUser, null);

  // But user still exists
  const stillExists = await prisma.user.findUnique({ where: { id: user.id } });
  t.truthy(stillExists);
});

test("user profile update works", async (t) => {
  // Create user
  const user = await prisma.user.create({
    data: {
      email: "profile@example.com",
      firstName: "Original",
      lastName: "Name",
      encryptedPassword: bcrypt.hashSync("password", 10)
    }
  });

  // Update profile
  const updatedUser = await prisma.user.update({
    where: { id: user.id },
    data: {
      firstName: "Updated",
      lastName: "Profile"
    }
  });

  t.is(updatedUser.firstName, "Updated");
  t.is(updatedUser.lastName, "Profile");
  t.is(updatedUser.email, "profile@example.com"); // Should remain unchanged
  t.true(updatedUser.updatedAt > user.updatedAt); // updatedAt should be newer
});

test("timestamps are automatically managed", async (t) => {
  const beforeCreate = new Date();

  const user = await prisma.user.create({
    data: {
      email: "timestamps@example.com",
      firstName: "Time",
      lastName: "Stamps",
      encryptedPassword: bcrypt.hashSync("password", 10)
    }
  });

  const afterCreate = new Date();

  // Check creation timestamps
  t.true(user.createdAt >= beforeCreate);
  t.true(user.createdAt <= afterCreate);
  t.true(user.updatedAt >= beforeCreate);
  t.true(user.updatedAt <= afterCreate);

  // Wait and update to test updatedAt behavior
  await new Promise(resolve => setTimeout(resolve, 100));
  const beforeUpdate = new Date();

  const updatedUser = await prisma.user.update({
    where: { id: user.id },
    data: { firstName: "Updated" }
  });

  const afterUpdate = new Date();

  // createdAt should remain the same, updatedAt should be newer
  t.deepEqual(updatedUser.createdAt, user.createdAt);
  t.true(updatedUser.updatedAt >= beforeUpdate);
  t.true(updatedUser.updatedAt <= afterUpdate);
  t.true(updatedUser.updatedAt > user.updatedAt);
});