Skip to content

Commit

Permalink
Merge pull request #78 from RevelioStartup/staging
Browse files Browse the repository at this point in the history
Staging
  • Loading branch information
emiriko authored May 17, 2024
2 parents 07919a5 + fb364a1 commit 17e24fd
Show file tree
Hide file tree
Showing 50 changed files with 1,148 additions and 132 deletions.
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
docker
.git
50 changes: 50 additions & 0 deletions .github/workflows/gcp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Deploy to GCP

on:
push:
branches:
- main

jobs:
Deployment:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Cloning repo
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Environment variables
run: |
touch .env
echo NEXT_PUBLIC_API_URL=${{ secrets.API_URL }} >> .env
cat .env
- name: Docker Login
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
buildkitd-flags: --debug

- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE_NAME }}:latest

- name: Push to GCP
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.GCP_VM_IP }}
username: ${{ secrets.GCP_VM_USERNAME }}
key: ${{ secrets.GCP_PRIVATE_KEY }}
script: |
docker login --username=${{ secrets.DOCKER_USERNAME }} --password=${{ secrets.DOCKER_PASSWORD }}
bash deploy.sh ${{ secrets.DOCKER_IMAGE_NAME }} ${{ secrets.DOCKER_USERNAME }}
67 changes: 67 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
FROM node:18-alpine AS base

# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* zpackage-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi


# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1

RUN \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
else echo "Lockfile not found." && exit 1; \
fi

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000

# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD HOSTNAME="0.0.0.0" node server.js
67 changes: 52 additions & 15 deletions __tests__/package/package-list-free.test.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,51 @@
import React from 'react'
import { render } from '@testing-library/react'
import { render, fireEvent } from '@testing-library/react'
import '@testing-library/jest-dom'
import PackageList from '@/app/package/page'
import { useCreateTransactionMutation } from '@/redux/api/paymentApi'

const premium_package = {
name: 'Premium Package',
price: 5000,
event_planner: true,
event_tracker: true,
event_timeline: true,
event_rundown: true,
ai_assistant: true,
}
jest.mock('@/redux/api/packageApi', () => ({
useGetPackageDetailQuery: jest.fn((id) => ({
data: {
name: 'Default Package',
price: 5000,
event_planner: true,
event_tracker: true,
event_timeline: true,
event_rundown: true,
ai_assistant: false,
},
})),
useGetPackageDetailQuery: jest.fn((id) => {
if (id === 1) {
return {
data: {
name: 'Free Package',
price: 0,
event_planner: true,
event_tracker: false,
event_timeline: false,
event_rundown: false,
ai_assistant: false,
},
}
} else if (id === 2) {
return {
data: premium_package,
}
}

return { data: null }
}),
}))

jest.mock('@/redux/api/paymentApi', () => ({
useCreateTransactionMutation: jest.fn(),
}))

jest.mock('@/redux/api/subscriptionApi', () => ({
useGetLatestSubscriptionQuery: jest.fn((id) => ({
data: {
id: 1,
plan: 'PREMIUM',
plan: { id: 1, name: 'FREE' },
start_date: '2024-05-05T13:30:00.000Z',
end_date: '2025-05-05T13:30:00.000Z',
user: 1,
Expand All @@ -31,9 +55,22 @@ jest.mock('@/redux/api/subscriptionApi', () => ({
}))

describe('PackageList component', () => {
it('renders correctly', () => {
const { getByTestId } = render(<PackageList />)
it('renders correctly for free user', () => {
const createTransactionMock = jest.fn()
;(useCreateTransactionMutation as jest.Mock).mockReturnValue([
createTransactionMock,
{
data: { token: 'token', redirect_url: 'https://example.midtrans.com' },
isLoading: false,
},
])
const { getByTestId, queryByTestId } = render(<PackageList />)
const packageDetail = getByTestId('package-detail')
expect(packageDetail).toBeInTheDocument()
const subscribeButton = getByTestId('choose-plan-button')
expect(subscribeButton).toBeInTheDocument()
expect(queryByTestId('subscribed-plan')).not.toBeInTheDocument()
fireEvent.click(subscribeButton)
expect(createTransactionMock).toHaveBeenCalled()
})
})
40 changes: 31 additions & 9 deletions __tests__/package/package-list-premium.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from 'react'
import { render } from '@testing-library/react'
import '@testing-library/jest-dom'
import PackageList from '@/app/package/page'
import { formatDate } from '@/utils/formatDate'
import { useCreateTransactionMutation } from '@/redux/api/paymentApi'

jest.mock('@/redux/api/packageApi', () => ({
useGetPackageDetailQuery: jest.fn((id) => ({
Expand All @@ -17,23 +19,43 @@ jest.mock('@/redux/api/packageApi', () => ({
})),
}))

jest.mock('@/redux/api/paymentApi', () => ({
useCreateTransactionMutation: jest.fn(),
}))

const latestSubs = {
id: 1,
plan: 'PREMIUM',
start_date: '2024-05-05T13:30:00.000Z',
end_date: '2025-05-05T13:30:00.000Z',
user: 1,
is_active: true,
}

jest.mock('@/redux/api/subscriptionApi', () => ({
useGetLatestSubscriptionQuery: jest.fn((id) => ({
data: {
id: 1,
plan: 'PREMIUM',
start_date: '2024-05-05T13:30:00.000Z',
end_date: '2025-05-05T13:30:00.000Z',
user: 1,
is_active: true,
},
data: latestSubs,
})),
}))

describe('PackageList component', () => {
beforeAll(() => {
jest.spyOn(window, 'open').mockImplementation()
})

it('renders correctly', () => {
const { getByTestId } = render(<PackageList />)
;(useCreateTransactionMutation as jest.Mock).mockReturnValue([
jest.fn(),
{
data: { token: 'string', redirect_url: 'https://example.com/midtrans' },
isLoading: false,
},
])
const { getByTestId, queryByTestId } = render(<PackageList />)
const packageDetail = getByTestId('package-detail')
expect(packageDetail).toBeInTheDocument()
expect(queryByTestId('choose-plan-button')).not.toBeInTheDocument()
expect(getByTestId('subscribed-plan')).toBeInTheDocument()
expect(window.open).toHaveBeenCalledWith('https://example.com/midtrans')
})
})
55 changes: 55 additions & 0 deletions __tests__/payment/payment-details-no-url.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import '@testing-library/jest-dom'
import { render, waitFor } from '@testing-library/react'
import PaymentPage from '@/app/payment/page'
import { useLazyGetTransactionQuery } from '@/redux/api/paymentApi'

jest.mock('next/navigation', () => ({
useSearchParams: jest.fn(() => ({
get: jest.fn(() => 'test_order_id'),
})),
}))

jest.mock('@/redux/api/paymentApi', () => ({
useLazyGetTransactionQuery: jest.fn(() => [
jest.fn(),
{
data: {
transaction_detail: {
package: { name: 'Test Package' },
payment_type: 'Test Payment Type',
payment_merchant: 'Test Payment Merchant',
midtrans_url: null,
},
},
isLoading: false,
},
]),
}))

describe('Payment Detail Page No Midtrans Url', () => {
beforeAll(() => {
jest.spyOn(window, 'open').mockImplementation()
})

it('disables button when midtrans_url is not available', async () => {
const { queryByTestId, getByRole } = render(<PaymentPage />)

await waitFor(() => expect(queryByTestId('loader')).not.toBeInTheDocument())
const continueButton = getByRole('button', {
name: 'Continue Transaction',
})
expect(continueButton).toBeDisabled()
})
it('shows loading state when query is still loading', async () => {
;(useLazyGetTransactionQuery as jest.Mock).mockReturnValue([
jest.fn(),
{
data: null,
isLoading: true,
},
])
const { queryByTestId } = render(<PaymentPage />)

await waitFor(() => expect(queryByTestId('loader')).toBeInTheDocument())
})
})
56 changes: 56 additions & 0 deletions __tests__/payment/payment-details-with-url.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import '@testing-library/jest-dom'
import { render, fireEvent, waitFor } from '@testing-library/react'
import PaymentPage from '@/app/payment/page'

jest.mock('next/navigation', () => ({
useSearchParams: jest.fn(() => ({
get: jest.fn(() => 'test_order_id'),
})),
}))

const mockTransaction = {
package: { name: 'Test Package' },
payment_type: 'Test Payment Type',
payment_merchant: 'Test Payment Merchant',
midtrans_url: 'https://example.com/midtrans',
}

jest.mock('@/redux/api/paymentApi', () => ({
useLazyGetTransactionQuery: jest.fn(() => [
jest.fn(),
{
data: {
transaction_detail: mockTransaction,
},
isLoading: false,
},
]),
}))

describe('Payment Detail Page With Midtrans Url', () => {
beforeAll(() => {
jest.spyOn(window, 'open').mockImplementation()
})

it('renders transaction detail', async () => {
const { queryByTestId, getByText } = render(<PaymentPage />)
await waitFor(() => {
expect(queryByTestId('loader')).not.toBeInTheDocument()
})

expect(getByText('Transaction detail')).toBeInTheDocument()
expect(getByText('Test Package')).toBeInTheDocument()
expect(getByText('Payment Type')).toBeInTheDocument()
expect(getByText('Merchant')).toBeInTheDocument()
expect(
getByText(mockTransaction.payment_type.toUpperCase())
).toBeInTheDocument()
expect(
getByText(mockTransaction.payment_merchant.toUpperCase())
).toBeInTheDocument()

fireEvent.click(getByText('Continue Transaction'))

expect(window.open).toHaveBeenCalledWith('https://example.com/midtrans')
})
})
Loading

0 comments on commit 17e24fd

Please sign in to comment.