Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Disable GCP autoconfiguration in test #8

Closed
wants to merge 4 commits into from
Closed
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
2 changes: 0 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Test
run: mvn test

- name: Build with Maven
run: mvn -B package -DskipTests
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Test
on:
pull_request:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: 'maven'

- name: Run integration test
run: mvn clean test
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Cove Web Service Repo

![Deployment](https://github.com/sprinthubmobile/cove_web/actions/workflows/deploy.yml/badge.svg?branch=main)
![Test](https://github.com/sprinthubmobile/cove_web/actions/workflows/test.yml/badge.svg)
## Before diving in 🙌

- Our recommend IDE for this project is IntelliJ, but you can use any IDE that supports Springboot
Expand Down
14 changes: 10 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,15 @@

<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>4.13.1</version>
<!-- <scope>test</scope>-->
<artifactId>de.flapdoodle.embed.mongo.spring30x</artifactId>
<version>4.11.0</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>

<!--Cache-->
Expand Down Expand Up @@ -226,7 +232,7 @@
</goals>
<configuration>
<sourceDirs>
<source>src/test/java</source>
<source>src/test/kotlin</source>
<source>target/generated-test-sources/test-annotations</source>
</sourceDirs>
</configuration>
Expand Down
54 changes: 27 additions & 27 deletions src/main/kotlin/ng/cove/web/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.google.cloud.secretmanager.v1.SecretManagerServiceClient
import com.google.cloud.secretmanager.v1.SecretVersionName
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import de.flapdoodle.embed.mongo.spring.autoconfigure.EmbeddedMongoAutoConfiguration
import ng.cove.web.component.SmsOtpService
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
Expand All @@ -15,15 +16,14 @@ import org.springframework.cache.annotation.EnableCaching
import org.springframework.cache.caffeine.CaffeineCacheManager
import org.springframework.context.ApplicationListener
import org.springframework.context.annotation.Bean
import org.springframework.core.env.StandardEnvironment
import org.springframework.scheduling.annotation.EnableAsync
import org.springframework.scheduling.annotation.EnableScheduling
import java.util.concurrent.TimeUnit

@EnableAsync
@EnableCaching
@EnableScheduling
@SpringBootApplication(exclude = [MongoDataAutoConfiguration::class])
@SpringBootApplication(exclude = [MongoDataAutoConfiguration::class, EmbeddedMongoAutoConfiguration::class])
class App {

@Bean
Expand All @@ -44,35 +44,35 @@ fun main(args: Array<String>) {

val startedEvent = ApplicationListener<ApplicationStartedEvent> { event ->

val profiles = StandardEnvironment().activeProfiles
val profiles = event.applicationContext.environment.activeProfiles
// profiles is sometimes empty when running on a production server because it
// has not been loaded from the application.properties file at this point
val runningOnProd = profiles.isEmpty() || profiles[0] == "prod"
val profile = profiles.getOrNull(0) ?: "prod"

if (!runningOnProd) {
// Init Firebase with Application default credentials from Gcloud or Firebase CLI
FirebaseApp.initializeApp()
} else {
when (profile) {
"test" -> {}
"dev" -> FirebaseApp.initializeApp()
"prod" -> {
// Get secrets from GCP
val gcpProjectId = "gatedaccessdev"
val firebaseSecret = SecretVersionName.of(gcpProjectId, "firebase-service-account", "1")
val termiiSecret = SecretVersionName.of(gcpProjectId, "termii-key", "1")

// Get secrets from GCP
val gcpProjectId = "gatedaccessdev"
val firebaseSecret = SecretVersionName.of(gcpProjectId, "firebase-service-account", "1")
val termiiSecret = SecretVersionName.of(gcpProjectId, "termii-key", "1")

// Auto-closable
SecretManagerServiceClient.create().use {
// Set Termii API Key to service
val termiiSecretPayload =
it.accessSecretVersion(termiiSecret).payload.data.toByteArray().inputStream()
val smsOtpService = event.applicationContext.getBean(SmsOtpService::class.java)
smsOtpService.termiiApiKey = String(termiiSecretPayload.readAllBytes())
// Init Firebase with service account
val serviceAccountStream =
it.accessSecretVersion(firebaseSecret).payload.data.toByteArray().inputStream()
val options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccountStream))
.build()
FirebaseApp.initializeApp(options)
// Auto-closable
SecretManagerServiceClient.create().use {
// Set Termii API Key to service
val termiiSecretPayload =
it.accessSecretVersion(termiiSecret).payload.data.toByteArray().inputStream()
val smsOtpService = event.applicationContext.getBean(SmsOtpService::class.java)
smsOtpService.termiiApiKey = String(termiiSecretPayload.readAllBytes())
// Init Firebase with service account
val serviceAccountStream =
it.accessSecretVersion(firebaseSecret).payload.data.toByteArray().inputStream()
val options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccountStream))
.build()
FirebaseApp.initializeApp(options)
}
}
}

Expand Down
10 changes: 9 additions & 1 deletion src/main/kotlin/ng/cove/web/config/MongoConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@ class MongoConfig(val context: WebApplicationContext) : AbstractMongoClientConfi

var embeddedMongo: TransitionWalker.ReachedState<RunningMongodProcess>? = null

override fun getDatabaseName(): String = "dev"
override fun getDatabaseName(): String {
val profiles = context.environment.activeProfiles
return if (profiles.getOrNull(0) == "test"){
"test"
}else {
"dev"
}
}

override fun mongoClient(): MongoClient {
val profiles = context.environment.activeProfiles
Expand All @@ -44,6 +51,7 @@ class MongoConfig(val context: WebApplicationContext) : AbstractMongoClientConfi
}
}
else -> {

// Embedded Mongo instance for testing
embeddedMongo = Mongod.instance().start(Version.Main.V7_0)
val serverAddress: ServerAddress = embeddedMongo!!.current().serverAddress
Expand Down
15 changes: 6 additions & 9 deletions src/main/kotlin/ng/cove/web/data/model/PhoneOtp.kt
Original file line number Diff line number Diff line change
@@ -1,31 +1,28 @@
package ng.cove.web.data.model

import com.mongodb.lang.NonNull
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.index.CompoundIndex
import org.springframework.data.mongodb.core.index.Indexed
import org.springframework.data.mongodb.core.mapping.Document
import org.springframework.data.mongodb.core.mapping.Field
import java.util.*

@Document("phone_otp")
@CompoundIndex(name = "phone_ref", def = "{'phone': 1, 'ref': 1}", unique = true)
class PhoneOtp {

constructor(phone: String, ref: String, type: UserType, expireAt: Date?){
this.phone = phone
this.ref = ref
this.type = type
this.expireAt = expireAt
}

@Id
var id: String? = null

@field:NonNull
var phone: String? = null

@Indexed(unique = true, sparse = true)
@field:NonNull
var ref: String? = null

private var type: UserType = UserType.Member
var type: UserType = UserType.Member

@field:Field("expire_at")
var expireAt: Date? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ import java.util.Date

interface MemberPhoneOtpRepo: MongoRepository<PhoneOtp, String> {
fun countByPhoneAndCreatedAtIsAfter(phone: String, createdAt: Date): Long
fun deleteAllByPhone(phone: String)
fun countByPhone(phone: String): Long
}
15 changes: 10 additions & 5 deletions src/main/kotlin/ng/cove/web/service/UserService.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package ng.cove.web.service

import com.fasterxml.jackson.databind.ObjectMapper
import com.google.firebase.auth.FirebaseAuth
import ng.cove.web.component.SmsOtpService
import ng.cove.web.data.model.PhoneOtp
Expand All @@ -20,8 +19,6 @@ import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service
import java.time.Duration
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.util.*


Expand Down Expand Up @@ -75,7 +72,12 @@ class UserService {
return if (otpResult != null) {
trialCount++
otpResult.dailyTrialLeft = maxDailyOtpTrial - trialCount
otpRepo.save(PhoneOtp(phone, otpResult.ref, userType, otpResult.expireAt))
val phoneOtp = PhoneOtp()
phoneOtp.phone = phone
phoneOtp.ref = otpResult.ref
phoneOtp.type = userType
phoneOtp.expireAt = otpResult.expireAt
otpRepo.save(phoneOtp)
ResponseEntity.ok().body(otpResult)
} else {
ResponseEntity.internalServerError().body("OTP provider error")
Expand Down Expand Up @@ -130,9 +132,12 @@ class UserService {
}

val firebaseAuth = FirebaseAuth.getInstance()
//Revoke refresh token for old devices if any

try {
//Revoke refresh token for old devices if any
firebaseAuth.revokeRefreshTokens(userId)
// Clear OTP limit for this user
otpRepo.deleteAllByPhone(phone)
} catch (_: Exception) {
}

Expand Down
8 changes: 6 additions & 2 deletions src/main/resources/application-prod.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ spring.mvc.async.request-timeout=20000
server.tomcat.threads.max=80
server.port=80


# Disable Swagger UI on production
springdoc.swagger-ui.enabled=false
springdoc.swagger-ui.enabled=false

# GCP Secret Manager
spring.cloud.gcp.secretmanager.enabled=true
# GCP Cloud Logging
spring.cloud.gcp.logging.enabled=true
8 changes: 3 additions & 5 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ spring.profiles.active=prod
otp.trial-limit=3
otp.expiry-mins=10
visitor.access-code.length=8
# GCP Secret Manager
spring.cloud.gcp.secretmanager.enabled=true
# GCP Cloud Logging
spring.cloud.gcp.logging.enabled=true
#This resolves CORS issues on Swagger UI
server.forward-headers-strategy=framework
server.forward-headers-strategy=framework

#de.flapdoodle.mongodb.embedded.version=6.0.5
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import ng.cove.web.data.repo.CommunityRepo
import ng.cove.web.data.repo.JoinRequestRepo
import ng.cove.web.data.repo.MemberPhoneOtpRepo
import ng.cove.web.data.repo.MemberRepo
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.TestInstance
import org.mockito.MockedStatic
import org.mockito.Mockito
import org.mockito.Mockito.mockStatic
Expand All @@ -21,14 +23,16 @@ import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.web.servlet.MockMvc


@SpringBootTest(classes = [App::class])
@ActiveProfiles("test")
@AutoConfigureMockMvc
class AppTests {
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AppTest {

@Autowired
lateinit var communityRepo: CommunityRepo
Expand All @@ -42,18 +46,14 @@ class AppTests {
@Autowired
lateinit var memberPhoneOtpRepo: MemberPhoneOtpRepo


lateinit var member: Member
lateinit var community: Community

@MockBean
lateinit var smsOtpService: SmsOtpService

@Autowired
lateinit var mockMvc: MockMvc

lateinit var staticFirebaseAuth: MockedStatic<FirebaseAuth>

lateinit var staticFirebaseAuth: MockedStatic<FirebaseAuth>
// Mocked FirebaseAuth for testing
val auth: FirebaseAuth = Mockito.mock(FirebaseAuth::class.java)

Expand All @@ -63,9 +63,15 @@ class AppTests {
final val faker = Faker()
val mapper = ObjectMapper().apply { propertyNamingStrategy = PropertyNamingStrategies.SNAKE_CASE }

lateinit var member: Member
lateinit var community: Community

@Autowired
lateinit var mongoTemplate : MongoTemplate

@BeforeEach
fun setUp() {

community = Community()
community.id = faker.random().hex(20)
community.name = "${faker.address().state()} Community"
Expand All @@ -81,23 +87,24 @@ class AppTests {
community.adminIds = setOf(member.id!!)

member.community = community
}


@BeforeAll
fun setupAll(){
// Mock FirebaseAuth
staticFirebaseAuth = mockStatic(FirebaseAuth::class.java)
staticFirebaseAuth.`when`<FirebaseAuth>(FirebaseAuth::getInstance).thenReturn(auth)

//TODO: Investigate why 'phone_otp' collection index is always duplicate in Embedded db
mongoTemplate.collectionNames.forEach {
mongoTemplate.getCollection(it).dropIndexes()
mongoTemplate.getCollection(it).drop()
}
}

@AfterEach
fun tearDown() {
@AfterAll
fun tearDownAll(){
staticFirebaseAuth.close()

// communityRepo.deleteAll()
// memberRepo.deleteAll()
// joinRequestRepo.deleteAll()
// memberPhoneOtpRepo.deleteAll()
}



}
Loading
Loading