diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..1d7c198
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[{*.kt,*.kts}]
+charset = utf-8
+end_of_line = lf
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
\ No newline at end of file
diff --git a/restaurants/README.md b/restaurants/README.md
index fc2ab6a..b835d34 100644
--- a/restaurants/README.md
+++ b/restaurants/README.md
@@ -1,36 +1,46 @@
# Restaurants
-This module is responsible for fetching restaurant items from USP (through [USP Restaurant API][1]) and persist them in a PostgresQL table, while also providing an API for the retrieval of these items.
+This module is responsible for fetching restaurant items from USP (through [USP Restaurant API][1]) and persist them in
+a PostgresQL table, while also providing an API for the retrieval of these items.
## Fetching from USP
-We trust the [USP Restaurant API][1] definitions, and with them the [USPRestaurantItemRepository][3] is able to fetch items from USP's api.
+We trust the [USP Restaurant API][1] definitions, and with them the [USPRestaurantItemRepository][3] is able to fetch
+items from USP's api.
### Parsing the menu
-It's important for this application to parse all values, as clients are going to expect a smarter output instead of just the menus (otherwise, why would this module be necessary?). For that we have the [MenuParsers][4]. We try with our best effort to parse the menus, however there is some difficulty as restaurants won't always report items in the same way.
+It's important for this application to parse all values, as clients are going to expect a smarter output instead of just
+the menus (otherwise, why would this module be necessary?). For that we have the [MenuParsers][4]. We try with our best
+effort to parse the menus, however there is some difficulty as restaurants won't always report items in the same way.
A future point of improvement is being able to parse menus without such a brittle and forced solution.
## Saving to Postgres
-Every item obtained from USP is piped into a PostgresQL item. This enables us to respond to clients even when USP's servers are down (which happen quite often).
+Every item obtained from USP is piped into a PostgresQL item. This enables us to respond to clients even when USP's
+servers are down (which happen quite often).
## Cache
-When an item is successfully obtained, it's first persisted on the Postgres table (see above), and then kept in an in-memory cache. This cache allow us to quickly respond users while keeping costs down.
+When an item is successfully obtained, it's first persisted on the Postgres table (see above), and then kept in an
+in-memory cache. This cache allow us to quickly respond users while keeping costs down.
-It's a good idea to use a cache here, as the data that clients want is usually the menus for the current week, making cache hits very likely.
+It's a good idea to use a cache here, as the data that clients want is usually the menus for the current week, making
+cache hits very likely.
## Restaurant Job
-In order to have all records available - even for restaurants that aren't accessed regularly - we continuously try to get all restaurants from USP, using a fixed rate schedule in [RestaurantJob class][5].
+In order to have all records available - even for restaurants that aren't accessed regularly - we continuously try to
+get all restaurants from USP, using a fixed rate schedule in [RestaurantJob class][5].
This will both refresh our current cache and save any new menus to our persistent storage.
-
[1]: https://github.com/JopiterApp/USP-Restaurant-API
+
[3]: src/main/kotlin/repository/usp/USPRestaurantItemRepository.kt
+
[4]: src/main/kotlin/repository/usp/MenuParser.kt
+
[5]: src/main/kotlin/RestaurantJob.kt
\ No newline at end of file
diff --git a/restaurants/build.gradle.kts b/restaurants/build.gradle.kts
index 3ae4bfa..29629d0 100644
--- a/restaurants/build.gradle.kts
+++ b/restaurants/build.gradle.kts
@@ -15,7 +15,6 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-import org.jetbrains.kotlin.noarg.gradle.NoArgExtension
plugins {
kotlin("plugin.spring")
@@ -51,4 +50,4 @@ flyway {
url = System.getenv("JDBC_URL") ?: "jdbc:postgresql://localhost:5432/jopiter"
user = System.getenv("DATABASE_USER") ?: "jopiter"
password = System.getenv("DATABASE_PASS") ?: "password"
-}
\ No newline at end of file
+}
diff --git a/restaurants/classifier/ClassifierDesign.md b/restaurants/classifier/ClassifierDesign.md
index d30aea2..af20b52 100644
--- a/restaurants/classifier/ClassifierDesign.md
+++ b/restaurants/classifier/ClassifierDesign.md
@@ -1,9 +1,9 @@
# Classifier Design
-
### Temos:
-Arquivos CSV onde a primeira coluna representa o nome do item e todas as outras colunas representam detalhes/categorias/classes dos itens.
+Arquivos CSV onde a primeira coluna representa o nome do item e todas as outras colunas representam
+detalhes/categorias/classes dos itens.
### Queremos
@@ -22,27 +22,37 @@ CSV:
João,Olhos Castanhos,Cabelo Preto
Tupla:
+
```
[nome = João, colunas=[Olhos Castanhos, Cabelo Preto]]
```
-
## Dados do Classificador
### Para treinar:
-Receberá todas as tuplas de um determinado grupo. Todas são semelhantes entre si, e possuem a mesma quantidade de colunas. O classificador só será capaz de comparar tipos que ele já conhece
+
+Receberá todas as tuplas de um determinado grupo. Todas são semelhantes entre si, e possuem a mesma quantidade de
+colunas. O classificador só será capaz de comparar tipos que ele já conhece
### Para prever/classificar
+
Receberá apenas o nome e terá que deduzir as outras tuplas a partir disso
## Implementação do classificador
+
1. CSV é convertido para N-Tuplas
2. Tuplas passam por tratamento
- 1. corpus <= Transformar o nome com BagOfWords (lib smile kotlin nlp bag) passando a lista de stop words (TODO: melhorar lista) e um stemmer (FIXME, explicar melhor) de palavras em português
- 2. vocabulario <= todos os nomes das tuplas, após passado as stop words e o stemmer
- 3. corpusVetorizado <= Com o corpus (nomes como bag of words) e o vocabulário, contar quantas vezes cada vocábulo aparece em cada documento, armazenando cada documento
- 4. dadosProntosParaModelagem <= Usamos o algorítimo tfidf para melhorar a definição de importância de cada termo de cada documento (FIXME, explicar melhor)
+ 1. corpus <= Transformar o nome com BagOfWords (lib smile kotlin nlp bag) passando a lista de stop words (TODO:
+ melhorar lista) e um stemmer (FIXME, explicar melhor) de palavras em português
+ 2. vocabulario <= todos os nomes das tuplas, após passado as stop words e o stemmer
+ 3. corpusVetorizado <= Com o corpus (nomes como bag of words) e o vocabulário, contar quantas vezes cada vocábulo
+ aparece em cada documento, armazenando cada documento
+ 4. dadosProntosParaModelagem <= Usamos o algorítimo tfidf para melhorar a definição de importância de cada termo de
+ cada documento (FIXME, explicar melhor)
3. Com os dados prontos para modelagem, treinamos um modelo diferente para cada uma das N colunas:
- 1. Primeiro através de um algorítimo OVR (One Versus Rest) (FIXME explicar melhor OVR)
- 2. Este OVR será alimentado com os dados organizados em um vetor de suporte (support vector machine / SVM) (FIXME adicionar artigo que explicar porque svm é o melhor)
-4. Estes modelos gerados compõem o clasificador, e podem ser exportados para armazenamento (economizando assim computação futura). É possível estimar pratos futuros neste classificador, tratando o novo documento da mesma forma que os antigos. Dessa forma temos as probabilidades que os modelos dão para cada uma das N colunas
\ No newline at end of file
+ 1. Primeiro através de um algorítimo OVR (One Versus Rest) (FIXME explicar melhor OVR)
+ 2. Este OVR será alimentado com os dados organizados em um vetor de suporte (support vector machine / SVM) (FIXME
+ adicionar artigo que explicar porque svm é o melhor)
+4. Estes modelos gerados compõem o clasificador, e podem ser exportados para armazenamento (economizando assim
+ computação futura). É possível estimar pratos futuros neste classificador, tratando o novo documento da mesma forma
+ que os antigos. Dessa forma temos as probabilidades que os modelos dão para cada uma das N colunas
\ No newline at end of file
diff --git a/restaurants/classifier/src/main/kotlin/app/jopiter/restaurants/classifier/StopwordsProvider.kt b/restaurants/classifier/src/main/kotlin/app/jopiter/restaurants/classifier/StopwordsProvider.kt
index 8fbe12d..939d2b7 100644
--- a/restaurants/classifier/src/main/kotlin/app/jopiter/restaurants/classifier/StopwordsProvider.kt
+++ b/restaurants/classifier/src/main/kotlin/app/jopiter/restaurants/classifier/StopwordsProvider.kt
@@ -1,7 +1,5 @@
package app.jopiter.restaurants.classifier
-import kotlin.streams.toList
-
@Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
class StopwordsProvider {
@@ -10,4 +8,5 @@ class StopwordsProvider {
}
}
-private fun portugueseStopwordsFile() = StopwordsProvider::class.java.classLoader.getResourceAsStream("portuguese_stopwords.txt")!!
+private fun portugueseStopwordsFile() =
+ StopwordsProvider::class.java.classLoader.getResourceAsStream("portuguese_stopwords.txt")!!
diff --git a/restaurants/classifier/src/test/kotlin/app/jopiter/restaurants/classifier/ClassifierTests.kt b/restaurants/classifier/src/test/kotlin/app/jopiter/restaurants/classifier/ClassifierTests.kt
index e419bd9..d2fae80 100644
--- a/restaurants/classifier/src/test/kotlin/app/jopiter/restaurants/classifier/ClassifierTests.kt
+++ b/restaurants/classifier/src/test/kotlin/app/jopiter/restaurants/classifier/ClassifierTests.kt
@@ -18,8 +18,6 @@
package app.jopiter.restaurants.classifier
-import app.jopiter.restaurants.classifier.ClassifiableRow
-import app.jopiter.restaurants.classifier.Classifier
import io.kotest.core.spec.style.FunSpec
import io.kotest.core.spec.style.funSpec
import io.kotest.matchers.doubles.shouldBeGreaterThanOrEqual
diff --git a/restaurants/src/main/kotlin/app/jopiter/restaurants/RestaurantController.kt b/restaurants/src/main/kotlin/app/jopiter/restaurants/RestaurantController.kt
index aea2c98..41cb0ba 100644
--- a/restaurants/src/main/kotlin/app/jopiter/restaurants/RestaurantController.kt
+++ b/restaurants/src/main/kotlin/app/jopiter/restaurants/RestaurantController.kt
@@ -21,7 +21,6 @@ package app.jopiter.restaurants
import app.jopiter.restaurants.classifier.RestaurantItemClassifier
import app.jopiter.restaurants.model.Campus
import app.jopiter.restaurants.model.ClassifiedRestaurantItem
-import app.jopiter.restaurants.model.RestaurantItem
import app.jopiter.restaurants.repository.RestaurantItemRepository
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
@@ -40,48 +39,56 @@ import java.time.LocalDate
@RestController
@RequestMapping("\${api.base.path}/restaurants")
class RestaurantController(
- private val restaurantItemRepository: RestaurantItemRepository,
- private val restaurantItemClassifier: RestaurantItemClassifier
+ private val restaurantItemRepository: RestaurantItemRepository,
+ private val restaurantItemClassifier: RestaurantItemClassifier
) {
- @Operation(
- summary = "List all restaurants and their campi",
- description = "List all restaurants that are available, including to which campus it belongs",
- tags = ["restaurant"],
- responses = [
- ApiResponse(responseCode = "200", content = [
- Content(array = ArraySchema(schema = Schema(implementation = Campus::class)),
- mediaType = "application/json")
- ])
+ @Operation(
+ summary = "List all restaurants and their campi",
+ description = "List all restaurants that are available, including to which campus it belongs",
+ tags = ["restaurant"],
+ responses = [
+ ApiResponse(
+ responseCode = "200", content = [
+ Content(
+ array = ArraySchema(schema = Schema(implementation = Campus::class)),
+ mediaType = "application/json"
+ )
]
- )
- @GetMapping
- fun list() = Campus.values().toList()
+ )
+ ]
+ )
+ @GetMapping
+ fun list() = Campus.values().toList()
- @Operation(
- summary = "List Items",
- description = "Retrieves all items for the chosen dates and restaurant",
- tags = ["restaurant"],
+ @Operation(
+ summary = "List Items",
+ description = "Retrieves all items for the chosen dates and restaurant",
+ tags = ["restaurant"],
- parameters = [
- Parameter(
- name = "restaurantId", description = "The restaurant ID as defined by /restaurants", required = true
- ),
- Parameter(
- name = "date", description = "The dates you want to fetch items for. ISO_LOCAL_DATE format (yyyy-MM-dd)"
- )
- ],
+ parameters = [
+ Parameter(
+ name = "restaurantId", description = "The restaurant ID as defined by /restaurants", required = true
+ ),
+ Parameter(
+ name = "date", description = "The dates you want to fetch items for. ISO_LOCAL_DATE format (yyyy-MM-dd)"
+ )
+ ],
- responses = [
- ApiResponse(responseCode = "200", content = [
- Content(array = ArraySchema(schema = Schema(implementation = ClassifiedRestaurantItem::class)),
- mediaType = "application/json")
- ])
+ responses = [
+ ApiResponse(
+ responseCode = "200", content = [
+ Content(
+ array = ArraySchema(schema = Schema(implementation = ClassifiedRestaurantItem::class)),
+ mediaType = "application/json"
+ )
]
- )
- @GetMapping("/items")
- fun items(
- @RequestParam("restaurantId") restaurantId: Int,
- @RequestParam("date") @DateTimeFormat(iso = DATE) dates: Set,
- ) = restaurantItemRepository.get(restaurantId, dates).map(restaurantItemClassifier::classify)
+ )
+ ]
+ )
+ @GetMapping("/items")
+ fun items(
+ @RequestParam("restaurantId") restaurantId: Int,
+ @RequestParam("date") @DateTimeFormat(iso = DATE) dates: Set,
+ ) = restaurantItemRepository.get(restaurantId, dates).map(restaurantItemClassifier::classify)
}
diff --git a/restaurants/src/main/kotlin/app/jopiter/restaurants/RestaurantJob.kt b/restaurants/src/main/kotlin/app/jopiter/restaurants/RestaurantJob.kt
index 4f0b1a9..b001025 100644
--- a/restaurants/src/main/kotlin/app/jopiter/restaurants/RestaurantJob.kt
+++ b/restaurants/src/main/kotlin/app/jopiter/restaurants/RestaurantJob.kt
@@ -23,21 +23,18 @@ import app.jopiter.restaurants.repository.RestaurantItemRepository
import org.slf4j.LoggerFactory
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
-import java.time.LocalDate.now
-import java.util.concurrent.ScheduledExecutorService
-import java.util.concurrent.TimeUnit.MINUTES
@Component
class RestaurantJob(
- private val repository: RestaurantItemRepository
+ private val repository: RestaurantItemRepository
) {
- private val logger = LoggerFactory.getLogger(this::class.java)
+ private val logger = LoggerFactory.getLogger(this::class.java)
- @Scheduled(fixedRate = 15_000)
- fun run() = Restaurant.entries.forEach {
- logger.info("Starting scheduled execution to fetch $it")
- repository.fetchFromUsp(it.id)
- logger.info("Finished scheduled execution to fetch $it")
- }
+ @Scheduled(fixedRate = 15_000)
+ fun run() = Restaurant.entries.forEach {
+ logger.info("Starting scheduled execution to fetch $it")
+ repository.fetchFromUsp(it.id)
+ logger.info("Finished scheduled execution to fetch $it")
+ }
}
diff --git a/restaurants/src/main/kotlin/app/jopiter/restaurants/classifier/RestaurantItemClassifier.kt b/restaurants/src/main/kotlin/app/jopiter/restaurants/classifier/RestaurantItemClassifier.kt
index 0cef658..1e21ccc 100644
--- a/restaurants/src/main/kotlin/app/jopiter/restaurants/classifier/RestaurantItemClassifier.kt
+++ b/restaurants/src/main/kotlin/app/jopiter/restaurants/classifier/RestaurantItemClassifier.kt
@@ -5,8 +5,8 @@ import app.jopiter.restaurants.model.DessertFoodGroup
import app.jopiter.restaurants.model.DessertItem
import app.jopiter.restaurants.model.DessertPreparation
import app.jopiter.restaurants.model.ProteinFoodGroup
-import app.jopiter.restaurants.model.ProteinPreparation
import app.jopiter.restaurants.model.ProteinItem
+import app.jopiter.restaurants.model.ProteinPreparation
import app.jopiter.restaurants.model.RestaurantItem
import app.jopiter.restaurants.model.VegetarianFoodGroup
import app.jopiter.restaurants.model.VegetarianItem
@@ -45,17 +45,29 @@ class RestaurantItemClassifier(
fun classifyProtein(item: String): ProteinItem {
val classification = proteinClassifier.classify(item)
- return ProteinItem(item, ProteinFoodGroup.find(classification.foodGroup), ProteinPreparation.find(classification.preparation))
+ return ProteinItem(
+ item,
+ ProteinFoodGroup.find(classification.foodGroup),
+ ProteinPreparation.find(classification.preparation)
+ )
}
fun classifyVegetarian(item: String): VegetarianItem {
val classification = vegetarianClassifier.classify(item)
- return VegetarianItem(item, VegetarianFoodGroup.find(classification.foodGroup), VegetarianPreparation.find(classification.preparation))
+ return VegetarianItem(
+ item,
+ VegetarianFoodGroup.find(classification.foodGroup),
+ VegetarianPreparation.find(classification.preparation)
+ )
}
fun classifyDessert(item: String): DessertItem {
val classification = dessertClassifier.classify(item)
- return DessertItem(item, DessertFoodGroup.find(classification.foodGroup), DessertPreparation.find(classification.preparation))
+ return DessertItem(
+ item,
+ DessertFoodGroup.find(classification.foodGroup),
+ DessertPreparation.find(classification.preparation)
+ )
}
-}
\ No newline at end of file
+}
diff --git a/restaurants/src/main/kotlin/app/jopiter/restaurants/model/ClassifiedRestaurantItem.kt b/restaurants/src/main/kotlin/app/jopiter/restaurants/model/ClassifiedRestaurantItem.kt
index 2284dfa..860638a 100644
--- a/restaurants/src/main/kotlin/app/jopiter/restaurants/model/ClassifiedRestaurantItem.kt
+++ b/restaurants/src/main/kotlin/app/jopiter/restaurants/model/ClassifiedRestaurantItem.kt
@@ -1,7 +1,6 @@
package app.jopiter.restaurants.model
import com.fasterxml.jackson.annotation.JsonFormat
-import org.springframework.stereotype.Component
import java.time.LocalDate
diff --git a/restaurants/src/main/kotlin/app/jopiter/restaurants/model/Restaurant.kt b/restaurants/src/main/kotlin/app/jopiter/restaurants/model/Restaurant.kt
index 3027caa..b844be3 100644
--- a/restaurants/src/main/kotlin/app/jopiter/restaurants/model/Restaurant.kt
+++ b/restaurants/src/main/kotlin/app/jopiter/restaurants/model/Restaurant.kt
@@ -44,23 +44,24 @@ import io.swagger.v3.oas.annotations.media.Schema
*/
@Schema(name = "Campus")
private interface CampusSer {
- @get:Schema(example = "Cidade Universitária") val campusName: String
- val restaurants: List
+ @get:Schema(example = "Cidade Universitária")
+ val campusName: String
+ val restaurants: List
}
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
@Schema(implementation = CampusSer::class)
enum class Campus(val campusName: String, val restaurants: List) {
- CidadeUniversitaria("Cidade Universitária", listOf(Central, PuSPC, Fisica, Quimicas)),
- QuadrilateroSaude("Quadrilátero Saúde", listOf(EscolaDeEnfermagem, SaudePublica)),
- LargoSaoFrancisco("Largo São Francisco", listOf(FacDireito)),
- UspLeste("USP Leste", listOf(Each)),
- Bauru("Campus de Bauru", listOf(Restaurant.Bauru)),
- LuizDeQueiroz("Campus \"Luiz de Queiroz\"", listOf(Piracicaba)),
- FernandoCosta("Campus \"Fernando Costa\"", listOf(Pirassununga)),
- SaoCarlos("Campus de São Carlos", listOf(Crhea, RestauranteArea1, RestauranteArea2)),
- RibeiraoPreto("Campus de Ribeirão Preto", listOf(CentralRibeirao)),
- Lorena("Campus de Lorena", listOf(Eel1, Eel2))
+ CidadeUniversitaria("Cidade Universitária", listOf(Central, PuSPC, Fisica, Quimicas)),
+ QuadrilateroSaude("Quadrilátero Saúde", listOf(EscolaDeEnfermagem, SaudePublica)),
+ LargoSaoFrancisco("Largo São Francisco", listOf(FacDireito)),
+ UspLeste("USP Leste", listOf(Each)),
+ Bauru("Campus de Bauru", listOf(Restaurant.Bauru)),
+ LuizDeQueiroz("Campus \"Luiz de Queiroz\"", listOf(Piracicaba)),
+ FernandoCosta("Campus \"Fernando Costa\"", listOf(Pirassununga)),
+ SaoCarlos("Campus de São Carlos", listOf(Crhea, RestauranteArea1, RestauranteArea2)),
+ RibeiraoPreto("Campus de Ribeirão Preto", listOf(CentralRibeirao)),
+ Lorena("Campus de Lorena", listOf(Eel1, Eel2))
}
/**
@@ -70,51 +71,53 @@ enum class Campus(val campusName: String, val restaurants: List) {
*/
@Schema(name = "Restaurant")
private interface RestaurantSer {
- @get:Schema(example = "9") val id: Int
- @get:Schema(example = "Químicas") val restaurantName: String
+ @get:Schema(example = "9")
+ val id: Int
+ @get:Schema(example = "Químicas")
+ val restaurantName: String
}
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
enum class Restaurant(val id: Int, val restaurantName: String) {
- // Cidade Universitária
- Central(6, "Central - Campus Butantã"),
- PuSPC(7, "PUSP-C - Campus Butantã"),
- Fisica(8, "Física - Campus Butantã"),
- Quimicas(9, "Químicas - Campus Butantã"),
+ // Cidade Universitária
+ Central(6, "Central - Campus Butantã"),
+ PuSPC(7, "PUSP-C - Campus Butantã"),
+ Fisica(8, "Física - Campus Butantã"),
+ Quimicas(9, "Químicas - Campus Butantã"),
- // Quatrilátero Saúde
- EscolaDeEnfermagem(12, "Escola de Enfermagem"),
- SaudePublica(11, "Fac. Saúde Pública"),
+ // Quatrilátero Saúde
+ EscolaDeEnfermagem(12, "Escola de Enfermagem"),
+ SaudePublica(11, "Fac. Saúde Pública"),
- // Largo São Francisco
- FacDireito(14, "Fac. Direito"),
+ // Largo São Francisco
+ FacDireito(14, "Fac. Direito"),
- // USP Leste
- Each(13, "EACH"),
+ // USP Leste
+ Each(13, "EACH"),
- // Baurú
- Bauru(20, "Bauru"),
+ // Baurú
+ Bauru(20, "Bauru"),
- // Luiz de Queiroz
- Piracicaba(1, "Piracicaba"),
+ // Luiz de Queiroz
+ Piracicaba(1, "Piracicaba"),
- // Fernando Costa
- Pirassununga(5, "Pirassununga"),
+ // Fernando Costa
+ Pirassununga(5, "Pirassununga"),
- // São Carlos
- Crhea(4, "Restaurante CRHEA"),
- RestauranteArea1(2, "Restaurante área 1"),
- RestauranteArea2(3, "Restaurante área 2"),
+ // São Carlos
+ Crhea(4, "Restaurante CRHEA"),
+ RestauranteArea1(2, "Restaurante área 1"),
+ RestauranteArea2(3, "Restaurante área 2"),
- // Ribeirão
- CentralRibeirao(19, "Restaurante Central -Campus RP"),
+ // Ribeirão
+ CentralRibeirao(19, "Restaurante Central -Campus RP"),
- // Lorena
- Eel1(17, "EEL - Área I"),
- Eel2(23, "EEL - Área II"),;
+ // Lorena
+ Eel1(17, "EEL - Área I"),
+ Eel2(23, "EEL - Área II"), ;
- companion object {
- fun getById(id: Int) = entries.first { it.id == id }
- }
+ companion object {
+ fun getById(id: Int) = entries.first { it.id == id }
+ }
}
diff --git a/restaurants/src/main/kotlin/app/jopiter/restaurants/model/RestaurantItem.kt b/restaurants/src/main/kotlin/app/jopiter/restaurants/model/RestaurantItem.kt
index 0d319e8..7587ff9 100644
--- a/restaurants/src/main/kotlin/app/jopiter/restaurants/model/RestaurantItem.kt
+++ b/restaurants/src/main/kotlin/app/jopiter/restaurants/model/RestaurantItem.kt
@@ -23,14 +23,14 @@ import com.fasterxml.jackson.annotation.JsonFormat.Shape.STRING
import java.time.LocalDate
data class RestaurantItem(
- val restaurantId: Int,
- @JsonFormat(pattern = "yyyy-MM-dd", shape = STRING) val date: LocalDate,
- val period: Period,
- val calories: Int?,
- val mainItem: String?,
- val vegetarianItem: String?,
- val dessertItem: String?,
- val mundaneItems: List,
- val unparsedMenu: String,
- val restaurantName: String = Restaurant.getById(restaurantId).name
+ val restaurantId: Int,
+ @JsonFormat(pattern = "yyyy-MM-dd", shape = STRING) val date: LocalDate,
+ val period: Period,
+ val calories: Int?,
+ val mainItem: String?,
+ val vegetarianItem: String?,
+ val dessertItem: String?,
+ val mundaneItems: List,
+ val unparsedMenu: String,
+ val restaurantName: String = Restaurant.getById(restaurantId).name
)
diff --git a/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/RestaurantItemRepository.kt b/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/RestaurantItemRepository.kt
index f0ee10a..3af02d4 100644
--- a/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/RestaurantItemRepository.kt
+++ b/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/RestaurantItemRepository.kt
@@ -26,20 +26,20 @@ import java.time.LocalDate
@Repository
class RestaurantItemRepository(
- private val uspRestaurantItemRepository: USPRestaurantItemRepository,
- private val postgresRestaurantItemRepository: PostgresRestaurantItemRepository
+ private val uspRestaurantItemRepository: USPRestaurantItemRepository,
+ private val postgresRestaurantItemRepository: PostgresRestaurantItemRepository
) {
- fun get(restaurantId: Int, dates: Set) = dates.flatMap { fetch(restaurantId, it) }.toSet()
+ fun get(restaurantId: Int, dates: Set) = dates.flatMap { fetch(restaurantId, it) }.toSet()
- private fun fetch(restaurantId: Int, date: LocalDate) =
- fetchFromPostgres(restaurantId, date)
+ private fun fetch(restaurantId: Int, date: LocalDate) =
+ fetchFromPostgres(restaurantId, date)
- fun fetchFromUsp(restaurantId: Int): Set {
- val items = uspRestaurantItemRepository.fetch(restaurantId)
- postgresRestaurantItemRepository.put(items)
- return items
- }
+ fun fetchFromUsp(restaurantId: Int): Set {
+ val items = uspRestaurantItemRepository.fetch(restaurantId)
+ postgresRestaurantItemRepository.put(items)
+ return items
+ }
- private fun fetchFromPostgres(restaurantId: Int, date: LocalDate) =
- postgresRestaurantItemRepository.get(restaurantId, date)
+ private fun fetchFromPostgres(restaurantId: Int, date: LocalDate) =
+ postgresRestaurantItemRepository.get(restaurantId, date)
}
diff --git a/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/postgres/KtormConfiguration.kt b/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/postgres/KtormConfiguration.kt
index b58c467..679ab64 100644
--- a/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/postgres/KtormConfiguration.kt
+++ b/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/postgres/KtormConfiguration.kt
@@ -9,5 +9,6 @@ import javax.sql.DataSource
class KtormConfiguration(
private val datasource: DataSource
) {
- @Bean fun database() = Database.connectWithSpringSupport(datasource)
-}
\ No newline at end of file
+ @Bean
+ fun database() = Database.connectWithSpringSupport(datasource)
+}
diff --git a/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/postgres/PostgresRestaurantItemRepository.kt b/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/postgres/PostgresRestaurantItemRepository.kt
index 0f84f17..6037302 100644
--- a/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/postgres/PostgresRestaurantItemRepository.kt
+++ b/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/postgres/PostgresRestaurantItemRepository.kt
@@ -9,15 +9,12 @@ import org.ktorm.entity.add
import org.ktorm.entity.filter
import org.ktorm.entity.map
import org.ktorm.entity.sequenceOf
-import org.ktorm.entity.toList
-import org.ktorm.entity.toSet
import org.ktorm.entity.update
import org.ktorm.schema.Table
import org.ktorm.schema.date
import org.ktorm.schema.enum
import org.ktorm.schema.int
import org.ktorm.schema.varchar
-import org.ktorm.support.postgresql.insertOrUpdate
import org.ktorm.support.postgresql.textArray
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository
@@ -37,7 +34,7 @@ class PostgresRestaurantItemRepository(
fun put(restaurantItem: RestaurantItem) {
val items = get(restaurantItem.restaurantId, restaurantItem.date, restaurantItem.period)
- if(items.isEmpty()) {
+ if (items.isEmpty()) {
restaurantItems.add(restaurantItem.toEntity())
} else {
restaurantItems.update(restaurantItem.toEntity())
@@ -46,7 +43,7 @@ class PostgresRestaurantItemRepository(
fun get(restaurantId: Int, date: LocalDate, period: Period? = null): Set {
val query = restaurantItems.filter { it.date eq date }.filter { it.restaurantId eq restaurantId }
- if(period != null) query.filter { it.period eq period }
+ if (period != null) query.filter { it.period eq period }
return query.map { it.toItem() }.toSet()
}
diff --git a/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/usp/MenuParser.kt b/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/usp/MenuParser.kt
index c9d02c1..3e3be75 100644
--- a/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/usp/MenuParser.kt
+++ b/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/usp/MenuParser.kt
@@ -26,150 +26,153 @@ import java.time.LocalDate
import kotlin.text.RegexOption.IGNORE_CASE
fun interface MenuParser {
- fun parse(menu: String): Menu
+ fun parse(menu: String): Menu
}
val luizDeQueirozParser = MenuParser {
- val items = it.cleanItems()
- if(items.size < 3) return@MenuParser closedMenuParser.parse(it)
- val (main, veg, dessert) = items.find("Prato principal:", "Opção Vegetariana:", "Sobremesa:")
- Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
+ val items = it.cleanItems()
+ if (items.size < 3) return@MenuParser closedMenuParser.parse(it)
+ val (main, veg, dessert) = items.find("Prato principal:", "Opção Vegetariana:", "Sobremesa:")
+ Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
}
val centralSaoCarlosParser = MenuParser {
- val items = it.cleanItems()
- if(items.size < 3) return@MenuParser closedMenuParser.parse(it)
- val (veg, dessert) = items.find("principal:", "Sobremesa:")
- val main = items.getOrNull(items.findIndex(veg) - 1)?.cleanString()
- Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
+ val items = it.cleanItems()
+ if (items.size < 3) return@MenuParser closedMenuParser.parse(it)
+ val (veg, dessert) = items.find("principal:", "Sobremesa:")
+ val main = items.getOrNull(items.findIndex(veg) - 1)?.cleanString()
+ Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
}
val pirassunungaParser = MenuParser {
- val items = it.cleanItems()
- if(items.size < 3) return@MenuParser closedMenuParser.parse(it)
- val (main, veg, des) = items.find("Prato Principal:", "Opção Vegetariana:", "Sobremesa:")
- Menu(main, veg, des, items.cleanStrings(main, veg, des), it)
+ val items = it.cleanItems()
+ if (items.size < 3) return@MenuParser closedMenuParser.parse(it)
+ val (main, veg, des) = items.find("Prato Principal:", "Opção Vegetariana:", "Sobremesa:")
+ Menu(main, veg, des, items.cleanStrings(main, veg, des), it)
}
val centralParser = MenuParser {
- val items = it.cleanItems()
- if(items.size < 3) return@MenuParser closedMenuParser.parse(it)
- val veg = items.find("Opção:").single()
- val main = items.getOrNull(items.findIndex(veg) - 1)?.cleanString()
- val dessert = items.getOrNull(items.lastIndex - 2)?.cleanString()
- Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
+ val items = it.cleanItems()
+ if (items.size < 3) return@MenuParser closedMenuParser.parse(it)
+ val veg = items.find("Opção:").single()
+ val main = items.getOrNull(items.findIndex(veg) - 1)?.cleanString()
+ val dessert = items.getOrNull(items.lastIndex - 2)?.cleanString()
+ Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
}
val quadrilateroParser = MenuParser {
- val items = it.cleanItems()
- if(items.size < 3) return@MenuParser closedMenuParser.parse(it)
- val veg = items.find("Opção:").single()
- val main = items.getOrNull(items.findIndex(veg) - 1)?.cleanString()
- val dessert = items.getOrNull(items.lastIndex - 3)?.cleanString()
- Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
+ val items = it.cleanItems()
+ if (items.size < 3) return@MenuParser closedMenuParser.parse(it)
+ val veg = items.find("Opção:").single()
+ val main = items.getOrNull(items.findIndex(veg) - 1)?.cleanString()
+ val dessert = items.getOrNull(items.lastIndex - 3)?.cleanString()
+ Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
}
val eachParser = MenuParser {
- val items = it.cleanItems()
- if(items.size < 3) return@MenuParser closedMenuParser.parse(it)
- val veg = items.find("Opção:").single()
- val main = items.getOrNull(items.findIndex(veg) - 1)?.cleanString()
- val dessert = items.getOrNull(items.lastIndex - 4)?.cleanString()
- Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
+ val items = it.cleanItems()
+ if (items.size < 3) return@MenuParser closedMenuParser.parse(it)
+ val veg = items.find("Opção:").single()
+ val main = items.getOrNull(items.findIndex(veg) - 1)?.cleanString()
+ val dessert = items.getOrNull(items.lastIndex - 4)?.cleanString()
+ Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
}
val largoSaoFranciscoParser = MenuParser {
- val items = it.cleanItems()
- if(items.size < 3) return@MenuParser closedMenuParser.parse(it)
- val veg = items.find("Opção:").single()
- val main = items.getOrNull(items.findIndex(veg) - 1)?.cleanString()
- val dessert = items.getOrNull(items.lastIndex - 3)?.cleanString()
- Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
+ val items = it.cleanItems()
+ if (items.size < 3) return@MenuParser closedMenuParser.parse(it)
+ val veg = items.find("Opção:").single()
+ val main = items.getOrNull(items.findIndex(veg) - 1)?.cleanString()
+ val dessert = items.getOrNull(items.lastIndex - 3)?.cleanString()
+ Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
}
val centralRibeiraoParser = MenuParser {
- val items = it.cleanItems()
- if(items.size < 3) return@MenuParser closedMenuParser.parse(it)
- val veg = items.find("veg:").single()
- val main = items.getOrNull(items.findIndex(veg) - 1)?.cleanString()
- val des = items.getOrNull(items.lastIndex - 4)?.cleanString()
- Menu(main, veg, des, items.cleanStrings(main, veg, des), it)
+ val items = it.cleanItems()
+ if (items.size < 3) return@MenuParser closedMenuParser.parse(it)
+ val veg = items.find("veg:").single()
+ val main = items.getOrNull(items.findIndex(veg) - 1)?.cleanString()
+ val des = items.getOrNull(items.lastIndex - 4)?.cleanString()
+ Menu(main, veg, des, items.cleanStrings(main, veg, des), it)
}
val bauruParser = MenuParser {
- val items = it.cleanItems()
- if(items.size < 3) return@MenuParser closedMenuParser.parse(it)
- val main = items[0].cleanString()
- val veg = items[1].cleanString()
- val dessert = items[items.lastIndex - 1].cleanString()
- Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
+ val items = it.cleanItems()
+ if (items.size < 3) return@MenuParser closedMenuParser.parse(it)
+ val main = items[0].cleanString()
+ val veg = items[1].cleanString()
+ val dessert = items[items.lastIndex - 1].cleanString()
+ Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
}
val lorenaParser = MenuParser {
- val items = it.cleanItems()
- if(items.size < 3) return@MenuParser closedMenuParser.parse(it)
- val veg = items.find("Opção:").single()
- val main = items.getOrNull(items.findIndex(veg) - 1)?.cleanString()
- val dessert = items.getOrNull(items.lastIndex - 2)?.cleanString()
- Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
+ val items = it.cleanItems()
+ if (items.size < 3) return@MenuParser closedMenuParser.parse(it)
+ val veg = items.find("Opção:").single()
+ val main = items.getOrNull(items.findIndex(veg) - 1)?.cleanString()
+ val dessert = items.getOrNull(items.lastIndex - 2)?.cleanString()
+ Menu(main, veg, dessert, items.cleanStrings(main, veg, dessert), it)
}
val closedMenuParser = MenuParser { Menu(null, null, null, emptyList(), it) }
private fun List.find(vararg containing: String) =
- filter { str -> containing.any { str.contains(it, true) } }.map { it.cleanString() }
+ filter { str -> containing.any { str.contains(it, true) } }.map { it.cleanString() }
-private fun List.findIndex(value: String?) = if(value == null) -1 else indexOfFirst { it.contains(value, true) }
+private fun List.findIndex(value: String?) =
+ if (value == null) -1 else indexOfFirst { it.contains(value, true) }
private fun List.cleanStrings(vararg specialItems: String?) =
- (map { it.cleanString() } - specialItems.toSet()).filterNot { it.isNullOrBlank() }.filterNotNull()
+ (map { it.cleanString() } - specialItems.toSet()).filterNot { it.isNullOrBlank() }.filterNotNull()
private fun String.cleanItems() =
- replace("\n(", " (")
- .split("\n", "/")
- .map { it.replace(Regex("[()]*\\d*,*\\.*\\d*\\s*kcal[(())]*", IGNORE_CASE), "") }
- .map { it.trim(',') }
- .filter { it.isNotBlank() }
- .filterNot { it.contains("Marmitex", true) }
+ replace("\n(", " (")
+ .split("\n", "/")
+ .map { it.replace(Regex("[()]*\\d*,*\\.*\\d*\\s*kcal[(())]*", IGNORE_CASE), "") }
+ .map { it.trim(',') }
+ .filter { it.isNotBlank() }
+ .filterNot { it.contains("Marmitex", true) }
private fun String.cleanString() =
- replace("c/", "com", true).substringAfter(":").trim().lowercase().replaceFirstChar { it.titlecase() }
+ replace("c/", "com", true).substringAfter(":").trim().lowercase().replaceFirstChar { it.titlecase() }
data class Menu(
- val mainItem: String?,
- val vegetarianItem: String?,
- val dessertItem: String?,
- val mundaneItems: List,
- val unparsedMenu: String
+ val mainItem: String?,
+ val vegetarianItem: String?,
+ val dessertItem: String?,
+ val mundaneItems: List,
+ val unparsedMenu: String
)
@Suppress("FunctionNaming")
fun RestaurantItem(restaurantId: Int, date: LocalDate, period: Period, calories: Int?, menu: Menu) = menu.run {
- RestaurantItem(
- restaurantId, date, period, calories, mainItem, vegetarianItem, dessertItem, mundaneItems, unparsedMenu
- )
+ RestaurantItem(
+ restaurantId, date, period, calories, mainItem, vegetarianItem, dessertItem, mundaneItems, unparsedMenu
+ )
}
-@Configuration class ParsersConfig {
- @Bean fun parsers() = parsers
+@Configuration
+class ParsersConfig {
+ @Bean
+ fun parsers() = parsers
}
val parsers: Map = mapOf(
- 1 to luizDeQueirozParser,
- 2 to centralSaoCarlosParser,
- 3 to centralSaoCarlosParser,
- 5 to pirassunungaParser,
- 6 to centralParser,
- 7 to centralParser,
- 8 to centralParser,
- 9 to centralParser,
- 11 to quadrilateroParser,
- 12 to quadrilateroParser,
- 13 to eachParser,
- 14 to largoSaoFranciscoParser,
- 17 to lorenaParser,
- 19 to centralRibeiraoParser,
- 20 to bauruParser,
- 23 to lorenaParser,
+ 1 to luizDeQueirozParser,
+ 2 to centralSaoCarlosParser,
+ 3 to centralSaoCarlosParser,
+ 5 to pirassunungaParser,
+ 6 to centralParser,
+ 7 to centralParser,
+ 8 to centralParser,
+ 9 to centralParser,
+ 11 to quadrilateroParser,
+ 12 to quadrilateroParser,
+ 13 to eachParser,
+ 14 to largoSaoFranciscoParser,
+ 17 to lorenaParser,
+ 19 to centralRibeiraoParser,
+ 20 to bauruParser,
+ 23 to lorenaParser,
) + listOf(4, 10).map { it to closedMenuParser }
diff --git a/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/usp/USPRestaurantItemRepository.kt b/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/usp/USPRestaurantItemRepository.kt
index d8704fe..607a754 100644
--- a/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/usp/USPRestaurantItemRepository.kt
+++ b/restaurants/src/main/kotlin/app/jopiter/restaurants/repository/usp/USPRestaurantItemRepository.kt
@@ -33,40 +33,40 @@ private const val RequestTimeoutMillis = 2_000
@Repository
class USPRestaurantItemRepository(
- @Value("\${usp.base.url}") val uspBaseUrl: String,
- private val parsers: Map,
- @Value("\${usp.hash}")private val uspHash: String
+ @Value("\${usp.base.url}") val uspBaseUrl: String,
+ private val parsers: Map,
+ @Value("\${usp.hash}") private val uspHash: String
) {
- fun fetch(restaurantId: Int): Set {
- val (_, _, result) = "$uspBaseUrl/menu/$restaurantId"
- .httpPost(listOf("hash" to uspHash))
- .timeoutRead(RequestTimeoutMillis)
- .responseObject()
+ fun fetch(restaurantId: Int): Set {
+ val (_, _, result) = "$uspBaseUrl/menu/$restaurantId"
+ .httpPost(listOf("hash" to uspHash))
+ .timeoutRead(RequestTimeoutMillis)
+ .responseObject()
- return result.fold(
- { it.toRestaurantItems(restaurantId, parsers[restaurantId] ?: return@fold emptySet()) },
- { if (it.causedByInterruption) emptySet() else throw it }
- )
- }
+ return result.fold(
+ { it.toRestaurantItems(restaurantId, parsers[restaurantId] ?: return@fold emptySet()) },
+ { if (it.causedByInterruption) emptySet() else throw it }
+ )
+ }
- private class MenuResponse(val meals: List) {
+ private class MenuResponse(val meals: List) {
- fun toRestaurantItems(restaurantId: Int, parser: MenuParser) = meals.flatMap {
- try {
- listOf(
- RestaurantItem(restaurantId, it.localDate, Lunch, it.lunch.calories, parser.parse(it.lunch.menu)),
- RestaurantItem(restaurantId, it.localDate, Dinner, it.dinner.calories, parser.parse(it.dinner.menu))
- )
- } catch (_: Exception) {
- emptySet()
- }
- }.toSet()
- }
+ fun toRestaurantItems(restaurantId: Int, parser: MenuParser) = meals.flatMap {
+ try {
+ listOf(
+ RestaurantItem(restaurantId, it.localDate, Lunch, it.lunch.calories, parser.parse(it.lunch.menu)),
+ RestaurantItem(restaurantId, it.localDate, Dinner, it.dinner.calories, parser.parse(it.dinner.menu))
+ )
+ } catch (_: Exception) {
+ emptySet()
+ }
+ }.toSet()
+ }
- private data class MenuMeals(val dinner: MenuPeriod, val lunch: MenuPeriod, val date: String) {
- val localDate: LocalDate by lazy { parse(date, ofPattern("dd/MM/yyyy")) }
- }
+ private data class MenuMeals(val dinner: MenuPeriod, val lunch: MenuPeriod, val date: String) {
+ val localDate: LocalDate by lazy { parse(date, ofPattern("dd/MM/yyyy")) }
+ }
- private data class MenuPeriod(val menu: String, val calories: Int? = null)
+ private data class MenuPeriod(val menu: String, val calories: Int? = null)
}
diff --git a/restaurants/src/main/resources/application-restaurants.properties b/restaurants/src/main/resources/application-restaurants.properties
index a28ad34..97d75af 100644
--- a/restaurants/src/main/resources/application-restaurants.properties
+++ b/restaurants/src/main/resources/application-restaurants.properties
@@ -15,6 +15,5 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
#
-
usp.base.url=https://uspdigital.usp.br/rucard/servicos
usp.hash=596df9effde6f877717b4e81fdb2ca9f
\ No newline at end of file
diff --git a/restaurants/src/main/resources/db/migration/V0__Create_Database.sql b/restaurants/src/main/resources/db/migration/V0__Create_Database.sql
index 043471b..314fd5a 100644
--- a/restaurants/src/main/resources/db/migration/V0__Create_Database.sql
+++ b/restaurants/src/main/resources/db/migration/V0__Create_Database.sql
@@ -2,16 +2,16 @@ CREATE TYPE period AS ENUM ('Lunch', 'Dinner');
CREATE TABLE restaurant_item
(
- restaurant_id int not null,
- restaurant_name text not null,
- date date not null,
- period period not null,
- calories int null default null,
- main_item text null default null,
- vegetarian_item text null default null,
- dessert_item text null default null,
+ restaurant_id int not null,
+ restaurant_name text not null,
+ date date not null,
+ period period not null,
+ calories int null default null,
+ main_item text null default null,
+ vegetarian_item text null default null,
+ dessert_item text null default null,
mundane_items text[] not null default ARRAY[]::text[],
- unparsed_menu text not null,
+ unparsed_menu text not null,
PRIMARY KEY (restaurant_id, date, period)
);
\ No newline at end of file
diff --git a/restaurants/src/test/kotlin/app/jopiter/restaurants/classifier/ProteinClassifierTest.kt b/restaurants/src/test/kotlin/app/jopiter/restaurants/classifier/ProteinClassifierTest.kt
index f104959..010799c 100644
--- a/restaurants/src/test/kotlin/app/jopiter/restaurants/classifier/ProteinClassifierTest.kt
+++ b/restaurants/src/test/kotlin/app/jopiter/restaurants/classifier/ProteinClassifierTest.kt
@@ -10,10 +10,15 @@ class ProteinClassifierTest : FunSpec({
test("Smoke test") {
val result = target.classify("Almôndega acebolada ao shoyo")
- result shouldBe ProteinClassification("bovina", "ao molho leve", "alcatra, almôndega, bife de contra filé, bife de patinho, coxão duro, lagarto, patinho/coxão mole", "Marrom/Bege")
+ result shouldBe ProteinClassification(
+ "bovina",
+ "ao molho leve",
+ "alcatra, almôndega, bife de contra filé, bife de patinho, coxão duro, lagarto, patinho/coxão mole",
+ "Marrom/Bege"
+ )
result.foodGroup shouldBe "bovina"
result.preparation shouldBe "ao molho leve"
}
-})
\ No newline at end of file
+})
diff --git a/restaurants/src/test/kotlin/app/jopiter/restaurants/classifier/RestaurantItemClassifierTest.kt b/restaurants/src/test/kotlin/app/jopiter/restaurants/classifier/RestaurantItemClassifierTest.kt
index 98fa149..db726de 100644
--- a/restaurants/src/test/kotlin/app/jopiter/restaurants/classifier/RestaurantItemClassifierTest.kt
+++ b/restaurants/src/test/kotlin/app/jopiter/restaurants/classifier/RestaurantItemClassifierTest.kt
@@ -5,16 +5,16 @@ import io.kotest.core.spec.style.FunSpec
class RestaurantItemClassifierTest : FunSpec({
- val proteinClassifier = ProteinClassifier("classified_items/protein.csv".loadCsv())
- val vegetarianClassifier = VegetarianClassifier("classified_items/vegetarian.csv".loadCsv())
- val dessertClassifier = DessertClassifier("classified_items/dessert.csv".loadCsv())
+ val proteinClassifier = ProteinClassifier("classified_items/protein.csv".loadCsv())
+ val vegetarianClassifier = VegetarianClassifier("classified_items/vegetarian.csv".loadCsv())
+ val dessertClassifier = DessertClassifier("classified_items/dessert.csv".loadCsv())
- val target = RestaurantItemClassifier(proteinClassifier, vegetarianClassifier, dessertClassifier)
+ val target = RestaurantItemClassifier(proteinClassifier, vegetarianClassifier, dessertClassifier)
- test("Warms up without trowing exceptions") {
- shouldNotThrowAny { target.warmupModel() }
- }
+ test("Warms up without trowing exceptions") {
+ shouldNotThrowAny { target.warmupModel() }
+ }
})
diff --git a/restaurants/src/test/kotlin/app/jopiter/restaurants/model/RestaurantTest.kt b/restaurants/src/test/kotlin/app/jopiter/restaurants/model/RestaurantTest.kt
index 4b9ec8d..b772895 100644
--- a/restaurants/src/test/kotlin/app/jopiter/restaurants/model/RestaurantTest.kt
+++ b/restaurants/src/test/kotlin/app/jopiter/restaurants/model/RestaurantTest.kt
@@ -30,32 +30,32 @@ import io.kotest.matchers.shouldBe
@Tags("Daily")
class RestaurantTest : FunSpec({
- context("Restaurants model reflects what USP returns") {
- val restaurantGroups = fetchRestaurantGroupsFromUSP()
-
- test("Should have same number of campi") {
- Campus.values() shouldBeSameSizeAs restaurantGroups
- }
-
- test("Should have a 1 to 1 mapping between USP's response and our model") {
- val allRestaurants = restaurantGroups.flatMap { it.restaurants }
- allRestaurants.forAll { (alias, id) ->
- Restaurant.getById(id).restaurantName shouldBe alias
- }
- }
+ context("Restaurants model reflects what USP returns") {
+ val restaurantGroups = fetchRestaurantGroupsFromUSP()
+
+ test("Should have same number of campi") {
+ Campus.values() shouldBeSameSizeAs restaurantGroups
}
- test("Should return restaurant by id") {
- val restaurant = Restaurant.values().random()
- Restaurant.getById(restaurant.id) shouldBe restaurant
+ test("Should have a 1 to 1 mapping between USP's response and our model") {
+ val allRestaurants = restaurantGroups.flatMap { it.restaurants }
+ allRestaurants.forAll { (alias, id) ->
+ Restaurant.getById(id).restaurantName shouldBe alias
+ }
}
+ }
+
+ test("Should return restaurant by id") {
+ val restaurant = Restaurant.values().random()
+ Restaurant.getById(restaurant.id) shouldBe restaurant
+ }
})
private fun fetchRestaurantGroupsFromUSP() =
- "https://uspdigital.usp.br/rucard/servicos/restaurants".httpPost()
- .header(Headers.CONTENT_TYPE, "application/x-www-form-urlencoded")
- .body("hash=596df9effde6f877717b4e81fdb2ca9f")
- .responseObject>().third.get()
+ "https://uspdigital.usp.br/rucard/servicos/restaurants".httpPost()
+ .header(Headers.CONTENT_TYPE, "application/x-www-form-urlencoded")
+ .body("hash=596df9effde6f877717b4e81fdb2ca9f")
+ .responseObject>().third.get()
data class RestaurantGroup(val name: String, val restaurants: List)
data class IndividualRestaurant(val alias: String, val id: Int)
diff --git a/restaurants/src/test/kotlin/app/jopiter/restaurants/repository/RestaurantItemRepositoryTest.kt b/restaurants/src/test/kotlin/app/jopiter/restaurants/repository/RestaurantItemRepositoryTest.kt
index 6845125..c68e61a 100644
--- a/restaurants/src/test/kotlin/app/jopiter/restaurants/repository/RestaurantItemRepositoryTest.kt
+++ b/restaurants/src/test/kotlin/app/jopiter/restaurants/repository/RestaurantItemRepositoryTest.kt
@@ -29,66 +29,69 @@ import io.kotest.core.spec.style.ShouldSpec
import io.kotest.extensions.testcontainers.JdbcTestContainerExtension
import io.kotest.extensions.time.ConstantNowTestListener
import io.kotest.matchers.shouldBe
-import io.mockk.called
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.flywaydb.core.Flyway
import org.ktorm.database.Database
import org.testcontainers.containers.PostgreSQLContainer
-import java.time.DayOfWeek.*
+import java.time.DayOfWeek.WEDNESDAY
import java.time.LocalDate
import java.time.LocalDate.now
class RestaurantItemRepositoryIntegrationTest : ShouldSpec({
- val postgres = install(JdbcTestContainerExtension(PostgreSQLContainer("postgres:16")))
- val flyway = Flyway.configure().cleanDisabled(false).dataSource(postgres).load()
+ val postgres = install(JdbcTestContainerExtension(PostgreSQLContainer("postgres:16")))
+ val flyway = Flyway.configure().cleanDisabled(false).dataSource(postgres).load()
- beforeSpec {
- flyway.clean()
- flyway.migrate()
- }
+ beforeSpec {
+ flyway.clean()
+ flyway.migrate()
+ }
- val postgresRepository = PostgresRestaurantItemRepository(Database.connect(postgres))
+ val postgresRepository = PostgresRestaurantItemRepository(Database.connect(postgres))
- val uspRepository = USPRestaurantItemRepository("https://uspdigital.usp.br/rucard/servicos", parsers, "596df9effde6f877717b4e81fdb2ca9f")
+ val uspRepository = USPRestaurantItemRepository(
+ "https://uspdigital.usp.br/rucard/servicos",
+ parsers,
+ "596df9effde6f877717b4e81fdb2ca9f"
+ )
- val target = RestaurantItemRepository(uspRepository, postgresRepository)
+ val target = RestaurantItemRepository(uspRepository, postgresRepository)
- should("Persist all items to the database") {
- val firstItemResult = target.get(13, setOf(LocalDate.of(2024, 3, 4)))
- val secondItemResult = target.get(13, setOf(LocalDate.of(2024, 3, 4)))
+ should("Persist all items to the database") {
+ val firstItemResult = target.get(13, setOf(LocalDate.of(2024, 3, 4)))
+ val secondItemResult = target.get(13, setOf(LocalDate.of(2024, 3, 4)))
- firstItemResult shouldBe secondItemResult
- }
+ firstItemResult shouldBe secondItemResult
+ }
})
class RestaurantItemRepositoryTest : ShouldSpec({
- val uspRepository = mockk()
- val postgresRepository = mockk(relaxed = true)
+ val uspRepository = mockk()
+ val postgresRepository = mockk(relaxed = true)
- val target = RestaurantItemRepository(uspRepository, postgresRepository)
+ val target = RestaurantItemRepository(uspRepository, postgresRepository)
- listener(ConstantNowTestListener(now().with(WEDNESDAY)))
+ listener(ConstantNowTestListener(now().with(WEDNESDAY)))
- should("Save any value fetched from USP to Postgres") {
- val today = now()
- val tomorrow = today.plusDays(1)
+ should("Save any value fetched from USP to Postgres") {
+ val today = now()
+ val tomorrow = today.plusDays(1)
- every { uspRepository.fetch(1) } returns setOf(dummyRestaurantItem(today), dummyRestaurantItem(tomorrow))
+ every { uspRepository.fetch(1) } returns setOf(dummyRestaurantItem(today), dummyRestaurantItem(tomorrow))
- target.fetchFromUsp(1)
+ target.fetchFromUsp(1)
- verify(exactly = 1) { postgresRepository.put(setOf(dummyRestaurantItem(today), dummyRestaurantItem(tomorrow))) }
- }
+ verify(exactly = 1) { postgresRepository.put(setOf(dummyRestaurantItem(today), dummyRestaurantItem(tomorrow))) }
+ }
- isolationMode = InstancePerTest
+ isolationMode = InstancePerTest
})
private fun dummyRestaurantItem(date: LocalDate) =
- RestaurantItem(1, date, Lunch, 0, "main", "veg", "des", emptyList(), "")
\ No newline at end of file
+ RestaurantItem(1, date, Lunch, 0, "main", "veg", "des", emptyList(), "")
diff --git a/restaurants/src/test/kotlin/app/jopiter/restaurants/repository/usp/MenuParsersTests.kt b/restaurants/src/test/kotlin/app/jopiter/restaurants/repository/usp/MenuParsersTests.kt
index 8449656..75ca835 100644
--- a/restaurants/src/test/kotlin/app/jopiter/restaurants/repository/usp/MenuParsersTests.kt
+++ b/restaurants/src/test/kotlin/app/jopiter/restaurants/repository/usp/MenuParsersTests.kt
@@ -24,267 +24,267 @@ import io.kotest.matchers.shouldBe
class MenuParsersTests : FunSpec({
- examples.forEach {
- context("Parser for menu ${it.restaurant}") {
- val parser = parsers.getValue(it.restaurant)
- val parsed = parser.parse(it.text)
+ examples.forEach {
+ context("Parser for menu ${it.restaurant}") {
+ val parser = parsers.getValue(it.restaurant)
+ val parsed = parser.parse(it.text)
- test("Main item parse") {
- parsed.mainItem shouldBe it.main
- }
+ test("Main item parse") {
+ parsed.mainItem shouldBe it.main
+ }
- test("Vegetarian item parse") {
- parsed.vegetarianItem shouldBe it.vegetarian
- }
+ test("Vegetarian item parse") {
+ parsed.vegetarianItem shouldBe it.vegetarian
+ }
- test("Dessert item parse") {
- parsed.dessertItem shouldBe it.dessert
- }
+ test("Dessert item parse") {
+ parsed.dessertItem shouldBe it.dessert
+ }
- xtest("Mundane item parse") {
- parsed.mundaneItems shouldContainExactlyInAnyOrder it.mundane.toList()
- }
- }
+ xtest("Mundane item parse") {
+ parsed.mundaneItems shouldContainExactlyInAnyOrder it.mundane.toList()
+ }
}
+ }
})
private val examples = listOf(
- Example(
- 1,
- "Acompanhamentos: Arroz / Feijão / Arroz Integral\nPrato principal: Frango assado\nOpção Vegetariana: Ovo pizzaiolo (lactose),\nGuarnição: Polenta\nSaladas: Catalonha, cenoura c/ beterraba\nSobremesa: Melão / Suco: Laranja",
- "Frango assado",
- "Ovo pizzaiolo (lactose)",
- "Melão",
- ""
- ),
- Example(
- 1,
- "Acompanhamentos: Arroz / Feijão Preto / Arroz Integral\nPrato principal: Bife de panela\nOpção Vegetariana: PTS ao sugo\nGuarnição: Macarrão alho e óleo (glúten),\nSaladas: Repolho roxo, abóbora\nSobremesa: Gelatina de limão / banana / Suco: Laranja",
- "Bife de panela",
- "Pts ao sugo",
- "Gelatina de limão",
- ""
- ),
- Example(
- 2,
- "Arroz/Feijão/ Arroz integral/Saladas: Diversas/Filé de coxa Assado/Opção do Prato Principal: PVT alemão/Polenta cremosa/Sobremesa: Romeu e Julieta/ Abacaxi/Mini Pão e Suco",
- "Filé de coxa assado",
- "Pvt alemão",
- "Romeu e julieta",
- ""
- ),
- Example(
- 2,
- "Arroz/Feijão/ Arroz integral/Saladas: Diversas/Estrogonofe de carne /Opção do Prato Principal: Estrogonofe de grão de bico/Batata palha/Sobremesa: Banana caramelada/ Mamão/Mini Pão e Suco",
- "Estrogonofe de carne",
- "Estrogonofe de grão de bico",
- "Banana caramelada",
- ""
- ),
- Example(
- 3,
- "Arroz/Feijão/ Arroz integral/Saladas: Diversas/Filé de coxa Assado/Opção do Prato Principal: PVT alemão/Polenta cremosa/Sobremesa: Romeu e Julieta/ Abacaxi/Mini Pão e Suco",
- "Filé de coxa assado",
- "Pvt alemão",
- "Romeu e julieta",
- ""
- ),
- Example(3, "Fechado", null, null, null),
- Example(
- 5,
- "Arroz branco/Arroz integral/Feijão carioca\nPrato principal: Carne assada ao molho madeira\nOpção vegetariana: Bolinho de lentilha (CONTÉM GLÚTEN), \nGuarnição: Purê de batata \nSalada: Alface - Cenoura - Abóbora cozida \nSobremesa: Paçoca\nSuco de Morango e Mini pão francês",
- "Carne assada ao molho madeira",
- "Bolinho de lentilha (contém glúten),",
- "Paçoca",
- ""
- ),
- Example(
- 5,
- "Arroz branco/Arroz integral/Feijão carioca\nPrato principal: Peixe a portuguesa\nOpção vegetariana: Escondidinho de PTS \nGuarnição: Creme de cenoura\nSalada: Alface - Pepino - Beterraba cozida \nSobremesa: Paçoca\nSuco de Morango e Mini pão francês",
- "Peixe a portuguesa",
- "Escondidinho de pts",
- "Paçoca",
- ""
- ),
- Example(
- 6,
- "Arroz / feijão / arroz integral\nFrango assado\nOpção: Ervilha com legumes\nRepolho refogado\nSalada de beterraba\nPão de mel\nMinipão / refresco",
- "Frango assado",
- "Ervilha com legumes",
- "Pão de mel",
- ""
- ),
- Example(
- 6,
- "Arroz / feijão preto / arroz integral\nBife de caçarola com molho ferrugem\nOpção: PVT com vagem\nBatata doce corada\nSalada de alface\nBanana\nMinipão / refresco",
- "Bife de caçarola com molho ferrugem",
- "Pvt com vagem",
- "Banana",
- ""
- ),
- Example(
- 7,
- "Arroz / feijão / arroz integral\nFilé de de peito de frango à pizzaiolo\nOpção: Omelete com alho-poró\nLegumes à juliana\nSalada de agrião\nSagú de maracujá\nMinipão / refresco",
- "Filé de de peito de frango à pizzaiolo",
- "Omelete com alho-poró",
- "Sagú de maracujá",
- ""
- ),
- Example(7, "Fechado", null, null, null, "mudane"),
- Example(
- 8,
- "Arroz / feijão / arroz integral\nFrango xadrez\nOpção: Bolinho de PVT\nAbobrinha com manjericão\nSalada de escarola\nRomeu e julieta\nMinipão / refresco",
- "Frango xadrez",
- "Bolinho de pvt",
- "Romeu e julieta",
-
- ),
- Example(
- 8,
- "Arroz / feijão / arroz integral\nLombo com molho de ervas\nOpção: PVT com cogumelos e tomate\nCenoura com vagem\nSalada de pepino\nLaranja\nMinipão / refresco",
- "Lombo com molho de ervas",
- "Pvt com cogumelos e tomate",
- "Laranja",
- ""
- ),
- Example(
- 9,
- "Arroz / feijão / arroz integral\nLagarto com molho ferrugem\nOpção: PVT com alho-poró\nCenoura com vagem\nSalada de almeirão\nGelatina de uva \nMinipão / refresco",
- "Lagarto com molho ferrugem",
- "Pvt com alho-poró",
- "Gelatina de uva",
- ""
- ),
- Example(
- 9,
- "Arroz / feijão / arroz integral\nLinguiça à escabeche\nOpção: Lasanha de brócolis com ricota\nQuiabo refogado\nSalada de escarola\nBanana \nMinipão / refresco",
- "Linguiça à escabeche",
- "Lasanha de brócolis com ricota",
- "Banana",
- ""
- ),
- Example(
- 11,
- "Arroz / feijão preto / arroz integral\nCupim assado com molho madeira\nOpção: Hambúrguer de feijão branco\nAbóbora ao forno\nSalada de mix de folhas\nCurau\nMinipão / refresco\n\n**Os Restaurantes Universitários não fornecem copos descartáveis. Tragam suas canecas.**",
- "Cupim assado com molho madeira",
- "Hambúrguer de feijão branco",
- "Curau",
- ""
- ),
- Example(
- 11,
- "Arroz / feijão / arroz integral\nFilé de coxa de frango com molho de gergelim\nOpção: PVT com gergelim\nBerinjela com uva passas\nSalada de alface\nMamão\nMinipão / refresco\n\n**Os Restaurantes Universitários não fornecem copos descartáveis. Tragam suas canecas.**",
- "Filé de coxa de frango com molho de gergelim",
- "Pvt com gergelim",
- "Mamão",
- ""
- ),
- Example(
- 12,
- "Arroz / feijão / arroz integral \nFilé de coxa de frango com molho de ervas\nOpção: Fricassê de PVT\nEscarola refogada\nSalada cenoura\nMaçã\nMinipão / refresco\n\n**Os Restaurantes Universitários não fornecem copos descartáveis. Tragam suas canecas.**",
- "Filé de coxa de frango com molho de ervas",
- "Fricassê de pvt",
- "Maçã",
- ""
- ),
- Example(12, "FECHADO", null, null, null, "mudane"),
- Example(
- 13,
- "Arroz / arroz integral / feijão carioca\nHambúrguer à pizzaiolo\nOpção: Hambúrguer de PVT à pizzaiolo\nBatata ao forno\nSaladas: Alface / beterraba / feijão fradinho \nMaria mole / banana\nMinipão / refresco\n\n**Os Restaurantes Universitários não fornecem copos descartáveis. Tragam suas canecas.**",
- "Hambúrguer à pizzaiolo",
- "Hambúrguer de pvt à pizzaiolo",
- "Maria mole",
- ""
- ),
- Example(
- 13,
- "Arroz / arroz integral / feijão carioca\nLagarto com molho roti\nOpção: Moussaka vegetariana\nVagem sauté\nSaladas: Catalonha / cenoura cozida / grão-de-bico\nSagu / melão\nMinipão / refresco\n\n**Os Restaurantes Universitários não fornecem copos descartáveis. Tragam suas canecas.**",
- "Lagarto com molho roti",
- "Moussaka vegetariana",
- "Sagu",
- ""
- ),
- Example(
- 14,
- "Arroz / arroz integral / feijão carioca\nHambúrguer a pizzaiolo \nOpção: Hambúrguer de PVT\nBatata corada\nSalada de alface\nGoiabada\nMinipão / refresco\n\n**Os Restaurantes Universitários não fornecem copos descartáveis. Tragam suas canecas.**",
- "Hambúrguer a pizzaiolo",
- "Hambúrguer de pvt",
- "Goiabada",
- ""
- ),
- Example(
- 14,
- "Arroz / arroz integral / feijão carioca\nLagarto com molho roti\nOpção: Moussaka vegetariana\nVagem sauté\nSalada de catalonha\nBanana\nMinipão / refresco\n\n**Os Restaurantes Universitários não fornecem copos descartáveis. Tragam suas canecas.**",
- "Lagarto com molho roti",
- "Moussaka vegetariana",
- "Banana",
- ""
- ),
- Example(
- 17,
- "Arroz / feijão preto / arroz integral\nSalsicha americana\nOpção: Grão-de-bico com tomate, cebola e salsa\nMacarrão ao alho e óleo\nSalada de alface\nGelatina com maçã\nMinipão / refresco",
- "Salsicha americana",
- "Grão-de-bico com tomate, cebola e salsa",
- "Gelatina com maçã",
- ""
- ),
- Example(
- 17,
- "Arroz / feijão preto / arroz integral\nPanqueca de carne\nOpção: Panqueca de PVT\nChuchu com milho\nSalada de repolho\nMamão\nMinipão / refresco",
- "Panqueca de carne",
- "Panqueca de pvt",
- "Mamão",
- ""
- ),
- Example(
- 19,
- "Linguiça Assada \nOp veg: PTS Clara à Primavera\nMandioca Ensopada\nAlface\nBerinjela\nMaçã\nArroz / Feijão / Integral \nSuco de Abacaxi\n808 Kcal/ 755 Kcal",
- "Linguiça assada",
- "Pts clara à primavera",
- "Maçã",
- ""
- ),
- Example(
- 19,
- "Lagarto ao Molho Madeira\nOp veg: Hambúrguer de Soja\nRepolho Refogado\nAgrião com Almeirão \nPepino \nMelão \nArroz / Feijão / Integral \nSuco de Tangerina\n762 Kcal/ 702 Kcal",
- "Lagarto ao molho madeira",
- "Hambúrguer de soja",
- "Melão",
- ""
- ),
- Example(
- 20,
- "BOLINHO DE CARNE AO MOLHO BARBECUE\n(musculo),\n152 KCAL\nLASANHA DE BERINJELA\n122KCAL\nCHUCHU REFOGADO\n 19KCAL\nALFACE\n9 kcal\nREPOLHO VERDE\n25 KCAL\nABÓBORA COZIDA\n31 KCAL\nARROZ BRANCO\n138kcal\nARROZ INTEGRAL\n116kcal\nFEIJÃO CARIOCA\n86 kcal\nMINI PÃO FRANCES\n75 kcal\nMELANCIA26 KCAL\nSUCO DE UVA\n48 kcal",
- "Bolinho de carne ao molho barbecue (musculo)",
- "Lasanha de berinjela",
- "Melancia",
- ""
- ),
- Example(
- 20,
- "LINGUIÇA CHAPEADA\n143 KCAL\nQUIBE DE ABÓBORA PAULISTA COM PTS\n177 kcal\nFAROFA DE CEBOLA\n88KCAL\nALFACE\n9 kcal\nPEPINO\n24 kcal\nJILÓ\n27 KCAL\nARROZ BRANCO\n138kcal\nARROZ INTEGRAL\n116kcal\nFEIJÃO CARIOCA\n86 kcal\nMINI PÃO FRANCES\n75 kcal\nMELANCIA\n30 KCAL\nSUCO DE UVA\n48 kcal",
- "Linguiça chapeada",
- "Quibe de abóbora paulista com pts",
- "Melancia",
- ""
- ),
- Example(
- 23,
- "Arroz / feijão preto / arroz integral\nSalsicha americana\nOpção: Grão-de-bico com tomate, cebola e salsa\nMacarrão ao alho e óleo\nSalada de alface\nGelatina com maçã\nMinipão / refresco",
- "Salsicha americana",
- "Grão-de-bico com tomate, cebola e salsa",
- "Gelatina com maçã",
- ""
- ),
- Example(23, "Fechado", null, null, null, ""),
+ Example(
+ 1,
+ "Acompanhamentos: Arroz / Feijão / Arroz Integral\nPrato principal: Frango assado\nOpção Vegetariana: Ovo pizzaiolo (lactose),\nGuarnição: Polenta\nSaladas: Catalonha, cenoura c/ beterraba\nSobremesa: Melão / Suco: Laranja",
+ "Frango assado",
+ "Ovo pizzaiolo (lactose)",
+ "Melão",
+ ""
+ ),
+ Example(
+ 1,
+ "Acompanhamentos: Arroz / Feijão Preto / Arroz Integral\nPrato principal: Bife de panela\nOpção Vegetariana: PTS ao sugo\nGuarnição: Macarrão alho e óleo (glúten),\nSaladas: Repolho roxo, abóbora\nSobremesa: Gelatina de limão / banana / Suco: Laranja",
+ "Bife de panela",
+ "Pts ao sugo",
+ "Gelatina de limão",
+ ""
+ ),
+ Example(
+ 2,
+ "Arroz/Feijão/ Arroz integral/Saladas: Diversas/Filé de coxa Assado/Opção do Prato Principal: PVT alemão/Polenta cremosa/Sobremesa: Romeu e Julieta/ Abacaxi/Mini Pão e Suco",
+ "Filé de coxa assado",
+ "Pvt alemão",
+ "Romeu e julieta",
+ ""
+ ),
+ Example(
+ 2,
+ "Arroz/Feijão/ Arroz integral/Saladas: Diversas/Estrogonofe de carne /Opção do Prato Principal: Estrogonofe de grão de bico/Batata palha/Sobremesa: Banana caramelada/ Mamão/Mini Pão e Suco",
+ "Estrogonofe de carne",
+ "Estrogonofe de grão de bico",
+ "Banana caramelada",
+ ""
+ ),
+ Example(
+ 3,
+ "Arroz/Feijão/ Arroz integral/Saladas: Diversas/Filé de coxa Assado/Opção do Prato Principal: PVT alemão/Polenta cremosa/Sobremesa: Romeu e Julieta/ Abacaxi/Mini Pão e Suco",
+ "Filé de coxa assado",
+ "Pvt alemão",
+ "Romeu e julieta",
+ ""
+ ),
+ Example(3, "Fechado", null, null, null),
+ Example(
+ 5,
+ "Arroz branco/Arroz integral/Feijão carioca\nPrato principal: Carne assada ao molho madeira\nOpção vegetariana: Bolinho de lentilha (CONTÉM GLÚTEN), \nGuarnição: Purê de batata \nSalada: Alface - Cenoura - Abóbora cozida \nSobremesa: Paçoca\nSuco de Morango e Mini pão francês",
+ "Carne assada ao molho madeira",
+ "Bolinho de lentilha (contém glúten),",
+ "Paçoca",
+ ""
+ ),
+ Example(
+ 5,
+ "Arroz branco/Arroz integral/Feijão carioca\nPrato principal: Peixe a portuguesa\nOpção vegetariana: Escondidinho de PTS \nGuarnição: Creme de cenoura\nSalada: Alface - Pepino - Beterraba cozida \nSobremesa: Paçoca\nSuco de Morango e Mini pão francês",
+ "Peixe a portuguesa",
+ "Escondidinho de pts",
+ "Paçoca",
+ ""
+ ),
+ Example(
+ 6,
+ "Arroz / feijão / arroz integral\nFrango assado\nOpção: Ervilha com legumes\nRepolho refogado\nSalada de beterraba\nPão de mel\nMinipão / refresco",
+ "Frango assado",
+ "Ervilha com legumes",
+ "Pão de mel",
+ ""
+ ),
+ Example(
+ 6,
+ "Arroz / feijão preto / arroz integral\nBife de caçarola com molho ferrugem\nOpção: PVT com vagem\nBatata doce corada\nSalada de alface\nBanana\nMinipão / refresco",
+ "Bife de caçarola com molho ferrugem",
+ "Pvt com vagem",
+ "Banana",
+ ""
+ ),
+ Example(
+ 7,
+ "Arroz / feijão / arroz integral\nFilé de de peito de frango à pizzaiolo\nOpção: Omelete com alho-poró\nLegumes à juliana\nSalada de agrião\nSagú de maracujá\nMinipão / refresco",
+ "Filé de de peito de frango à pizzaiolo",
+ "Omelete com alho-poró",
+ "Sagú de maracujá",
+ ""
+ ),
+ Example(7, "Fechado", null, null, null, "mudane"),
+ Example(
+ 8,
+ "Arroz / feijão / arroz integral\nFrango xadrez\nOpção: Bolinho de PVT\nAbobrinha com manjericão\nSalada de escarola\nRomeu e julieta\nMinipão / refresco",
+ "Frango xadrez",
+ "Bolinho de pvt",
+ "Romeu e julieta",
+ ""
+ ),
+ Example(
+ 8,
+ "Arroz / feijão / arroz integral\nLombo com molho de ervas\nOpção: PVT com cogumelos e tomate\nCenoura com vagem\nSalada de pepino\nLaranja\nMinipão / refresco",
+ "Lombo com molho de ervas",
+ "Pvt com cogumelos e tomate",
+ "Laranja",
+ ""
+ ),
+ Example(
+ 9,
+ "Arroz / feijão / arroz integral\nLagarto com molho ferrugem\nOpção: PVT com alho-poró\nCenoura com vagem\nSalada de almeirão\nGelatina de uva \nMinipão / refresco",
+ "Lagarto com molho ferrugem",
+ "Pvt com alho-poró",
+ "Gelatina de uva",
+ ""
+ ),
+ Example(
+ 9,
+ "Arroz / feijão / arroz integral\nLinguiça à escabeche\nOpção: Lasanha de brócolis com ricota\nQuiabo refogado\nSalada de escarola\nBanana \nMinipão / refresco",
+ "Linguiça à escabeche",
+ "Lasanha de brócolis com ricota",
+ "Banana",
+ ""
+ ),
+ Example(
+ 11,
+ "Arroz / feijão preto / arroz integral\nCupim assado com molho madeira\nOpção: Hambúrguer de feijão branco\nAbóbora ao forno\nSalada de mix de folhas\nCurau\nMinipão / refresco\n\n**Os Restaurantes Universitários não fornecem copos descartáveis. Tragam suas canecas.**",
+ "Cupim assado com molho madeira",
+ "Hambúrguer de feijão branco",
+ "Curau",
+ ""
+ ),
+ Example(
+ 11,
+ "Arroz / feijão / arroz integral\nFilé de coxa de frango com molho de gergelim\nOpção: PVT com gergelim\nBerinjela com uva passas\nSalada de alface\nMamão\nMinipão / refresco\n\n**Os Restaurantes Universitários não fornecem copos descartáveis. Tragam suas canecas.**",
+ "Filé de coxa de frango com molho de gergelim",
+ "Pvt com gergelim",
+ "Mamão",
+ ""
+ ),
+ Example(
+ 12,
+ "Arroz / feijão / arroz integral \nFilé de coxa de frango com molho de ervas\nOpção: Fricassê de PVT\nEscarola refogada\nSalada cenoura\nMaçã\nMinipão / refresco\n\n**Os Restaurantes Universitários não fornecem copos descartáveis. Tragam suas canecas.**",
+ "Filé de coxa de frango com molho de ervas",
+ "Fricassê de pvt",
+ "Maçã",
+ ""
+ ),
+ Example(12, "FECHADO", null, null, null, "mudane"),
+ Example(
+ 13,
+ "Arroz / arroz integral / feijão carioca\nHambúrguer à pizzaiolo\nOpção: Hambúrguer de PVT à pizzaiolo\nBatata ao forno\nSaladas: Alface / beterraba / feijão fradinho \nMaria mole / banana\nMinipão / refresco\n\n**Os Restaurantes Universitários não fornecem copos descartáveis. Tragam suas canecas.**",
+ "Hambúrguer à pizzaiolo",
+ "Hambúrguer de pvt à pizzaiolo",
+ "Maria mole",
+ ""
+ ),
+ Example(
+ 13,
+ "Arroz / arroz integral / feijão carioca\nLagarto com molho roti\nOpção: Moussaka vegetariana\nVagem sauté\nSaladas: Catalonha / cenoura cozida / grão-de-bico\nSagu / melão\nMinipão / refresco\n\n**Os Restaurantes Universitários não fornecem copos descartáveis. Tragam suas canecas.**",
+ "Lagarto com molho roti",
+ "Moussaka vegetariana",
+ "Sagu",
+ ""
+ ),
+ Example(
+ 14,
+ "Arroz / arroz integral / feijão carioca\nHambúrguer a pizzaiolo \nOpção: Hambúrguer de PVT\nBatata corada\nSalada de alface\nGoiabada\nMinipão / refresco\n\n**Os Restaurantes Universitários não fornecem copos descartáveis. Tragam suas canecas.**",
+ "Hambúrguer a pizzaiolo",
+ "Hambúrguer de pvt",
+ "Goiabada",
+ ""
+ ),
+ Example(
+ 14,
+ "Arroz / arroz integral / feijão carioca\nLagarto com molho roti\nOpção: Moussaka vegetariana\nVagem sauté\nSalada de catalonha\nBanana\nMinipão / refresco\n\n**Os Restaurantes Universitários não fornecem copos descartáveis. Tragam suas canecas.**",
+ "Lagarto com molho roti",
+ "Moussaka vegetariana",
+ "Banana",
+ ""
+ ),
+ Example(
+ 17,
+ "Arroz / feijão preto / arroz integral\nSalsicha americana\nOpção: Grão-de-bico com tomate, cebola e salsa\nMacarrão ao alho e óleo\nSalada de alface\nGelatina com maçã\nMinipão / refresco",
+ "Salsicha americana",
+ "Grão-de-bico com tomate, cebola e salsa",
+ "Gelatina com maçã",
+ ""
+ ),
+ Example(
+ 17,
+ "Arroz / feijão preto / arroz integral\nPanqueca de carne\nOpção: Panqueca de PVT\nChuchu com milho\nSalada de repolho\nMamão\nMinipão / refresco",
+ "Panqueca de carne",
+ "Panqueca de pvt",
+ "Mamão",
+ ""
+ ),
+ Example(
+ 19,
+ "Linguiça Assada \nOp veg: PTS Clara à Primavera\nMandioca Ensopada\nAlface\nBerinjela\nMaçã\nArroz / Feijão / Integral \nSuco de Abacaxi\n808 Kcal/ 755 Kcal",
+ "Linguiça assada",
+ "Pts clara à primavera",
+ "Maçã",
+ ""
+ ),
+ Example(
+ 19,
+ "Lagarto ao Molho Madeira\nOp veg: Hambúrguer de Soja\nRepolho Refogado\nAgrião com Almeirão \nPepino \nMelão \nArroz / Feijão / Integral \nSuco de Tangerina\n762 Kcal/ 702 Kcal",
+ "Lagarto ao molho madeira",
+ "Hambúrguer de soja",
+ "Melão",
+ ""
+ ),
+ Example(
+ 20,
+ "BOLINHO DE CARNE AO MOLHO BARBECUE\n(musculo),\n152 KCAL\nLASANHA DE BERINJELA\n122KCAL\nCHUCHU REFOGADO\n 19KCAL\nALFACE\n9 kcal\nREPOLHO VERDE\n25 KCAL\nABÓBORA COZIDA\n31 KCAL\nARROZ BRANCO\n138kcal\nARROZ INTEGRAL\n116kcal\nFEIJÃO CARIOCA\n86 kcal\nMINI PÃO FRANCES\n75 kcal\nMELANCIA26 KCAL\nSUCO DE UVA\n48 kcal",
+ "Bolinho de carne ao molho barbecue (musculo)",
+ "Lasanha de berinjela",
+ "Melancia",
+ ""
+ ),
+ Example(
+ 20,
+ "LINGUIÇA CHAPEADA\n143 KCAL\nQUIBE DE ABÓBORA PAULISTA COM PTS\n177 kcal\nFAROFA DE CEBOLA\n88KCAL\nALFACE\n9 kcal\nPEPINO\n24 kcal\nJILÓ\n27 KCAL\nARROZ BRANCO\n138kcal\nARROZ INTEGRAL\n116kcal\nFEIJÃO CARIOCA\n86 kcal\nMINI PÃO FRANCES\n75 kcal\nMELANCIA\n30 KCAL\nSUCO DE UVA\n48 kcal",
+ "Linguiça chapeada",
+ "Quibe de abóbora paulista com pts",
+ "Melancia",
+ ""
+ ),
+ Example(
+ 23,
+ "Arroz / feijão preto / arroz integral\nSalsicha americana\nOpção: Grão-de-bico com tomate, cebola e salsa\nMacarrão ao alho e óleo\nSalada de alface\nGelatina com maçã\nMinipão / refresco",
+ "Salsicha americana",
+ "Grão-de-bico com tomate, cebola e salsa",
+ "Gelatina com maçã",
+ ""
+ ),
+ Example(23, "Fechado", null, null, null, ""),
)
private class Example(
- val restaurant: Int,
- val text: String,
- val main: String?,
- val vegetarian: String?,
- val dessert: String?,
- vararg val mundane: String
-)
\ No newline at end of file
+ val restaurant: Int,
+ val text: String,
+ val main: String?,
+ val vegetarian: String?,
+ val dessert: String?,
+ vararg val mundane: String
+)
diff --git a/restaurants/src/test/kotlin/app/jopiter/restaurants/repository/usp/USPRestaurantItemRepositoryTest.kt b/restaurants/src/test/kotlin/app/jopiter/restaurants/repository/usp/USPRestaurantItemRepositoryTest.kt
index 861dfe7..5ede777 100644
--- a/restaurants/src/test/kotlin/app/jopiter/restaurants/repository/usp/USPRestaurantItemRepositoryTest.kt
+++ b/restaurants/src/test/kotlin/app/jopiter/restaurants/repository/usp/USPRestaurantItemRepositoryTest.kt
@@ -21,9 +21,6 @@ package app.jopiter.restaurants.repository.usp
import app.jopiter.restaurants.model.Period.Dinner
import app.jopiter.restaurants.model.Period.Lunch
import app.jopiter.restaurants.model.RestaurantItem
-import app.jopiter.restaurants.repository.usp.Menu
-import app.jopiter.restaurants.repository.usp.MenuParser
-import app.jopiter.restaurants.repository.usp.USPRestaurantItemRepository
import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.shouldBe
@@ -32,7 +29,6 @@ import org.mockserver.integration.ClientAndServer.startClientAndServer
import org.mockserver.mock.OpenAPIExpectation.openAPIExpectation
import org.mockserver.model.Delay.seconds
import org.mockserver.model.HttpRequest.request
-import java.lang.RuntimeException
import java.time.LocalDate.of
private val uspRestaurantSpecUrl = "https://raw.githubusercontent.com/JopiterApp/USP-Restaurant-API/main/openapi.yaml"
@@ -40,43 +36,44 @@ private val specification = openAPIExpectation(uspRestaurantSpecUrl)
class USPRestaurantItemRepositoryTest : ShouldSpec({
- val mockServer = startClientAndServer().apply { upsert(specification) }
+ val mockServer = startClientAndServer().apply { upsert(specification) }
- val target = USPRestaurantItemRepository("http://localhost:${mockServer.localPort}", dummyParsers, "hash")
+ val target = USPRestaurantItemRepository("http://localhost:${mockServer.localPort}", dummyParsers, "hash")
- should("Parse a menu using the right parser") {
- val parsed = target.fetch(6)
+ should("Parse a menu using the right parser") {
+ val parsed = target.fetch(6)
- parsed shouldBe setOf(
- RestaurantItem(6, of(2020, 3, 9), Lunch, 1219, "foo", "bar", "baz", emptyList(), "", "Central"),
- RestaurantItem(6, of(2020, 3, 9), Dinner, 1056, "foo", "bar", "baz", emptyList(), "", "Central"),
- RestaurantItem(6, of(2020, 3, 13), Lunch, 1145, "foo", "bar", "baz", emptyList(), "", "Central"),
- RestaurantItem(6, of(2020, 3, 13), Dinner, 895, "foo", "bar", "baz", emptyList(), "", "Central"),
- RestaurantItem(6, of(2020, 3, 14), Lunch, 1236, "foo", "bar", "baz", emptyList(), "", "Central"),
- RestaurantItem(6, of(2020, 3, 14), Dinner, 0, "foo", "bar", "baz", emptyList(), "", "Central"),
- RestaurantItem(6, of(2020, 3, 15), Lunch, 0, "foo", "bar", "baz", emptyList(), "", "Central"),
- RestaurantItem(6, of(2020, 3, 15), Dinner, 0, "foo", "bar", "baz", emptyList(), "", "Central"),
- )
- }
+ parsed shouldBe setOf(
+ RestaurantItem(6, of(2020, 3, 9), Lunch, 1219, "foo", "bar", "baz", emptyList(), "", "Central"),
+ RestaurantItem(6, of(2020, 3, 9), Dinner, 1056, "foo", "bar", "baz", emptyList(), "", "Central"),
+ RestaurantItem(6, of(2020, 3, 13), Lunch, 1145, "foo", "bar", "baz", emptyList(), "", "Central"),
+ RestaurantItem(6, of(2020, 3, 13), Dinner, 895, "foo", "bar", "baz", emptyList(), "", "Central"),
+ RestaurantItem(6, of(2020, 3, 14), Lunch, 1236, "foo", "bar", "baz", emptyList(), "", "Central"),
+ RestaurantItem(6, of(2020, 3, 14), Dinner, 0, "foo", "bar", "baz", emptyList(), "", "Central"),
+ RestaurantItem(6, of(2020, 3, 15), Lunch, 0, "foo", "bar", "baz", emptyList(), "", "Central"),
+ RestaurantItem(6, of(2020, 3, 15), Dinner, 0, "foo", "bar", "baz", emptyList(), "", "Central"),
+ )
+ }
- should("Return nothing if the parser has an error") {
- target.fetch(7).shouldBeEmpty()
- }
+ should("Return nothing if the parser has an error") {
+ target.fetch(7).shouldBeEmpty()
+ }
- should("Timeout after 2s") {
- val delayedExpectations = mockServer.retrieveActiveExpectations(request()).map { it.apply{ httpResponse.withDelay(seconds(30)) } }
- mockServer.upsert(*delayedExpectations.toTypedArray())
+ should("Timeout after 2s") {
+ val delayedExpectations =
+ mockServer.retrieveActiveExpectations(request()).map { it.apply { httpResponse.withDelay(seconds(30)) } }
+ mockServer.upsert(*delayedExpectations.toTypedArray())
- target.fetch(6).shouldBeEmpty()
- }
+ target.fetch(6).shouldBeEmpty()
+ }
- afterSpec { mockServer.stop(); unmockkAll() }
+ afterSpec { mockServer.stop(); unmockkAll() }
})
private val dummyParsers = mapOf(
- 6 to MenuParser { Menu("foo", "bar", "baz", emptyList(), "") },
- 7 to MenuParser { throw InvalidMenuException(7) },
+ 6 to MenuParser { Menu("foo", "bar", "baz", emptyList(), "") },
+ 7 to MenuParser { throw InvalidMenuException(7) },
)
class InvalidMenuException(val menuId: Int) : RuntimeException()
diff --git a/restaurants/src/test/resources/logback.xml b/restaurants/src/test/resources/logback.xml
index b7f6efd..e1ea7c4 100644
--- a/restaurants/src/test/resources/logback.xml
+++ b/restaurants/src/test/resources/logback.xml
@@ -11,6 +11,6 @@
-
+
diff --git a/timetable/build.gradle.kts b/timetable/build.gradle.kts
index 42a41a8..c899f41 100644
--- a/timetable/build.gradle.kts
+++ b/timetable/build.gradle.kts
@@ -19,10 +19,10 @@
apply(plugin = "kotlin")
dependencies {
- // JSoup
- implementation("org.jsoup:jsoup:1.13.1")
+ // JSoup
+ implementation("org.jsoup:jsoup:1.13.1")
- // Selenium
- implementation("org.seleniumhq.selenium:selenium-firefox-driver:3.141.59")
- implementation("org.seleniumhq.selenium:htmlunit-driver:2.33.2")
-}
\ No newline at end of file
+ // Selenium
+ implementation("org.seleniumhq.selenium:selenium-firefox-driver:3.141.59")
+ implementation("org.seleniumhq.selenium:htmlunit-driver:2.33.2")
+}
diff --git a/timetable/src/main/kotlin/TimetableController.kt b/timetable/src/main/kotlin/TimetableController.kt
index 2b4aba5..8010266 100644
--- a/timetable/src/main/kotlin/TimetableController.kt
+++ b/timetable/src/main/kotlin/TimetableController.kt
@@ -32,34 +32,34 @@ import org.springframework.web.bind.annotation.RequestBody as SpringRequestBody
@RestController("\${api.base.path}/timetable")
class TimetableController(
- private val timetableRepository: TimetableRepository,
+ private val timetableRepository: TimetableRepository,
) {
- @Operation(
- summary = "Fetch a timetable from JupitereWeb",
- description = "Tries to login to user's account and obtain all information related to their timetable",
- tags = ["timetable"],
+ @Operation(
+ summary = "Fetch a timetable from JupitereWeb",
+ description = "Tries to login to user's account and obtain all information related to their timetable",
+ tags = ["timetable"],
- requestBody = RequestBody(content = [Content(schema = Schema(implementation = TimetableRequest::class))]),
+ requestBody = RequestBody(content = [Content(schema = Schema(implementation = TimetableRequest::class))]),
- responses = [
- ApiResponse(
- responseCode = "200",
- content = [Content(array = ArraySchema(schema = Schema(implementation = Subject::class)))]
- ),
- ApiResponse(responseCode = "401"),
- ApiResponse(responseCode = "503")
- ]
- )
- @PostMapping()
- fun timetable(@SpringRequestBody request: TimetableRequest): ResponseEntity> {
- val (user, password) = request
- return try {
- ResponseEntity.ok(timetableRepository.get(user, password))
- } catch (_: Throwable) {
- ResponseEntity.badRequest().build()
- }
+ responses = [
+ ApiResponse(
+ responseCode = "200",
+ content = [Content(array = ArraySchema(schema = Schema(implementation = Subject::class)))]
+ ),
+ ApiResponse(responseCode = "401"),
+ ApiResponse(responseCode = "503")
+ ]
+ )
+ @PostMapping()
+ fun timetable(@SpringRequestBody request: TimetableRequest): ResponseEntity> {
+ val (user, password) = request
+ return try {
+ ResponseEntity.ok(timetableRepository.get(user, password))
+ } catch (_: Throwable) {
+ ResponseEntity.badRequest().build()
}
+ }
- data class TimetableRequest(val user: String, val password: String)
+ data class TimetableRequest(val user: String, val password: String)
}
diff --git a/timetable/src/main/kotlin/repository/SubjectNameRepository.kt b/timetable/src/main/kotlin/repository/SubjectNameRepository.kt
index bd2b8de..c603be2 100644
--- a/timetable/src/main/kotlin/repository/SubjectNameRepository.kt
+++ b/timetable/src/main/kotlin/repository/SubjectNameRepository.kt
@@ -24,8 +24,8 @@ import org.springframework.stereotype.Repository
@Repository
class SubjectNameRepository {
- private val url = "https://uspdigital.usp.br/jupiterweb/obterDisciplina?nomdis=&sgldis="
+ private val url = "https://uspdigital.usp.br/jupiterweb/obterDisciplina?nomdis=&sgldis="
- operator fun get(code: String) =
- connect(url + code).get().selectFirst("b:contains($code)").text().substringAfter("-").trim()
+ operator fun get(code: String) =
+ connect(url + code).get().selectFirst("b:contains($code)").text().substringAfter("-").trim()
}
diff --git a/timetable/src/main/kotlin/repository/TimetableRepository.kt b/timetable/src/main/kotlin/repository/TimetableRepository.kt
index 2c66a00..cd4d44c 100644
--- a/timetable/src/main/kotlin/repository/TimetableRepository.kt
+++ b/timetable/src/main/kotlin/repository/TimetableRepository.kt
@@ -48,89 +48,90 @@ private const val JupiterLoginUrl = "https://uspdigital.usp.br/jupiterweb/webLog
@Repository
class TimetableRepository(
- private val subjectNameRepository: SubjectNameRepository
+ private val subjectNameRepository: SubjectNameRepository
) {
- init {
- setDriverProperty()
+ init {
+ setDriverProperty()
+ }
+
+ fun get(user: String, password: String): Set = with(firefoxDriver()) {
+ try {
+ navigateToTimetable(user, password)
+ val joinedCourses = waiting { Select(findElement(name("codpgm"))).takeIf { it.options.size > 1 } }
+ joinedCourses.options.drop(1).flatMap {
+ val timetable = parseCourse(it)
+ waiting { invisibilityOfElementLocated(className("blockOverlay")).apply(this) }
+ findElement(id("step1-tab")).click()
+ timetable.flatMap(TimetableRow::asSubjects)
+ }.toSet()
+ } finally {
+ close()
}
-
- fun get(user: String, password: String): Set = with(firefoxDriver()) {
- try {
- navigateToTimetable(user, password)
- val joinedCourses = waiting { Select(findElement(name("codpgm"))).takeIf { it.options.size > 1 } }
- joinedCourses.options.drop(1).flatMap {
- val timetable = parseCourse(it)
- waiting { invisibilityOfElementLocated(className("blockOverlay")).apply(this) }
- findElement(id("step1-tab")).click()
- timetable.flatMap(TimetableRow::asSubjects)
- }.toSet()
- } finally {
- close()
- }
- }
-
- private fun parseCourse(course: WebElement) = with(course) {
- click()
- findElement(id("buscar")).click()
- val timetable = findElement(id("tableGradeHoraria"))
- val rows = timetable.findElements(className("ui-widget-content"))
- rows.map { it.findElements(tagName("td")) }.map { TimetableRow(it.map { it.text }) }
+ }
+
+ private fun parseCourse(course: WebElement) = with(course) {
+ click()
+ findElement(id("buscar")).click()
+ val timetable = findElement(id("tableGradeHoraria"))
+ val rows = timetable.findElements(className("ui-widget-content"))
+ rows.map { it.findElements(tagName("td")) }.map { TimetableRow(it.map { it.text }) }
+ }
+
+ private fun FirefoxDriver.navigateToTimetable(user: String, password: String) = apply {
+ get(JupiterLoginUrl)
+ findElement(name("codpes")).sendKeys(user)
+ findElement(name("senusu")).sendKeys(password)
+ findElement(name("Submit")).click()
+ waiting { invisibilityOfElementLocated(className("ui-widget-overlay")) }
+ findElement(linkText("Grade horária")).click()
+ }
+
+ private fun firefoxDriver() = FirefoxDriver(FirefoxOptions().setHeadless(false))
+
+ private fun FirefoxDriver.waiting(block: () -> T?) =
+ WebDriverWait(this, Duration.ofSeconds(10)).until { block() }!!
+
+ @Suppress("LongParameterList")
+ inner class TimetableRow(
+ val start: String,
+ val end: String,
+ val mon: String,
+ val tue: String,
+ val wed: String,
+ val thu: String,
+ val fri: String,
+ val sat: String,
+ ) {
+ constructor(trs: List) : this(trs[0], trs[1], trs[2], trs[3], trs[4], trs[5], trs[6], trs[7])
+
+ fun asSubjects(): List {
+ val subjects = mutableListOf()
+ val time = if (start.isBlank() || end.isBlank()) MIN..MAX else parse(start)..parse(end)
+
+ if (mon.isNotBlank()) subjects += Subject(MONDAY, mon.code, subjectNameRepository[mon.code], time)
+ if (tue.isNotBlank()) subjects += Subject(TUESDAY, tue.code, subjectNameRepository[tue.code], time)
+ if (wed.isNotBlank()) subjects += Subject(WEDNESDAY, wed.code, subjectNameRepository[wed.code], time)
+ if (thu.isNotBlank()) subjects += Subject(THURSDAY, thu.code, subjectNameRepository[thu.code], time)
+ if (fri.isNotBlank()) subjects += Subject(FRIDAY, fri.code, subjectNameRepository[fri.code], time)
+ if (sat.isNotBlank()) subjects += Subject(SATURDAY, sat.code, subjectNameRepository[sat.code], time)
+
+ return subjects
}
- private fun FirefoxDriver.navigateToTimetable(user: String, password: String) = apply {
- get(JupiterLoginUrl)
- findElement(name("codpes")).sendKeys(user)
- findElement(name("senusu")).sendKeys(password)
- findElement(name("Submit")).click()
- waiting { invisibilityOfElementLocated(className("ui-widget-overlay")) }
- findElement(linkText("Grade horária")).click()
- }
-
- private fun firefoxDriver() = FirefoxDriver(FirefoxOptions().setHeadless(false))
-
- private fun FirefoxDriver.waiting(block: () -> T?) = WebDriverWait(this, Duration.ofSeconds(10)).until { block() }!!
-
- @Suppress("LongParameterList")
- inner class TimetableRow(
- val start: String,
- val end: String,
- val mon: String,
- val tue: String,
- val wed: String,
- val thu: String,
- val fri: String,
- val sat: String,
- ) {
- constructor(trs: List) : this(trs[0], trs[1], trs[2], trs[3], trs[4], trs[5], trs[6], trs[7])
-
- fun asSubjects(): List {
- val subjects = mutableListOf()
- val time = if(start.isBlank() || end.isBlank()) MIN..MAX else parse(start)..parse(end)
-
- if(mon.isNotBlank()) subjects += Subject(MONDAY, mon.code, subjectNameRepository[mon.code], time)
- if(tue.isNotBlank()) subjects += Subject(TUESDAY, tue.code, subjectNameRepository[tue.code], time)
- if(wed.isNotBlank()) subjects += Subject(WEDNESDAY, wed.code, subjectNameRepository[wed.code], time)
- if(thu.isNotBlank()) subjects += Subject(THURSDAY, thu.code, subjectNameRepository[thu.code], time)
- if(fri.isNotBlank()) subjects += Subject(FRIDAY, fri.code, subjectNameRepository[fri.code], time)
- if(sat.isNotBlank()) subjects += Subject(SATURDAY, sat.code, subjectNameRepository[sat.code], time)
-
- return subjects
- }
-
- private val String.code get() = substringBefore("-")
- }
+ private val String.code get() = substringBefore("-")
+ }
}
data class Subject(val dayOfWeek: DayOfWeek, val code: String, val name: String, val time: ClosedRange)
private fun setDriverProperty() {
- if(System.getProperties().containsKey("webdriver.gecko.driver")) return
- val driver = TimetableRepository::class.java.classLoader.getResourceAsStream("geckodriver")!!
- val path = createTempFile("gecko", "driver").apply {
- writeBytes(driver.readAllBytes())
- setExecutable(true)
- }.absolutePath
- System.setProperty("webdriver.gecko.driver", path)
+ if (System.getProperties().containsKey("webdriver.gecko.driver")) return
+ val driver = TimetableRepository::class.java.classLoader.getResourceAsStream("geckodriver")!!
+ val path = createTempFile("gecko", "driver").apply {
+ writeBytes(driver.readAllBytes())
+ setExecutable(true)
+ }.absolutePath
+ System.setProperty("webdriver.gecko.driver", path)
}
diff --git a/timetable/src/test/kotlin/repository/SubjectNameRepositoryTest.kt b/timetable/src/test/kotlin/repository/SubjectNameRepositoryTest.kt
index 8e603ff..ea8ad85 100644
--- a/timetable/src/test/kotlin/repository/SubjectNameRepositoryTest.kt
+++ b/timetable/src/test/kotlin/repository/SubjectNameRepositoryTest.kt
@@ -19,24 +19,23 @@
package app.jopiter.timetable.repository
import io.kotest.assertions.throwables.shouldThrowAny
-import io.kotest.core.spec.style.FunSpec
import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.shouldBe
class SubjectNameRepositoryTest : ShouldSpec({
- val target = SubjectNameRepository()
+ val target = SubjectNameRepository()
- should("Fetch the subjects name correctly") {
- target["ACH2017"] shouldBe "Projeto Supervisionado ou de Graduação I"
- target["ACH2076"] shouldBe "Segurança da Informação"
- target["PMT3100"] shouldBe "Fundamentos de Ciência e Engenharia dos Materiais"
- target["MAT2454"] shouldBe "Cálculo Diferencial e Integral II"
- target["PCS3617"] shouldBe "Estágio Cooperativo I"
- }
+ should("Fetch the subjects name correctly") {
+ target["ACH2017"] shouldBe "Projeto Supervisionado ou de Graduação I"
+ target["ACH2076"] shouldBe "Segurança da Informação"
+ target["PMT3100"] shouldBe "Fundamentos de Ciência e Engenharia dos Materiais"
+ target["MAT2454"] shouldBe "Cálculo Diferencial e Integral II"
+ target["PCS3617"] shouldBe "Estágio Cooperativo I"
+ }
- should("Throw exception when fetching an unknown subject") {
- shouldThrowAny { target["ablueblua"] }
- }
+ should("Throw exception when fetching an unknown subject") {
+ shouldThrowAny { target["ablueblua"] }
+ }
})
diff --git a/timetable/src/test/kotlin/repository/TimetableRepositoryTest.kt b/timetable/src/test/kotlin/repository/TimetableRepositoryTest.kt
index 186abcb..79366a3 100644
--- a/timetable/src/test/kotlin/repository/TimetableRepositoryTest.kt
+++ b/timetable/src/test/kotlin/repository/TimetableRepositoryTest.kt
@@ -33,32 +33,32 @@ import java.time.LocalTime.of
import kotlin.reflect.KClass
class UspVariablesCondition : EnabledCondition {
- override fun enabled(specKlass: KClass) = "USP_USER_1" in getenv() && "USP_USER_2" in getenv()
+ override fun enabled(specKlass: KClass) = "USP_USER_1" in getenv() && "USP_USER_2" in getenv()
}
@EnabledIf(UspVariablesCondition::class)
class TimetableRepositoryTest : ShouldSpec({
- val target = TimetableRepository(SubjectNameRepository())
+ val target = TimetableRepository(SubjectNameRepository())
- should("Return the right subjects (Test 1)") {
- val answer = target.get(getenv("USP_USER_1"), getenv("USP_PASSWORD_1"))
+ should("Return the right subjects (Test 1)") {
+ val answer = target.get(getenv("USP_USER_1"), getenv("USP_PASSWORD_1"))
- answer shouldBe setOf(
- Subject(MONDAY, "ACH2017", "Projeto Supervisionado ou de Graduação I", of(12, 0)..of(13, 0)),
- Subject(WEDNESDAY, "ACH2076", "Segurança da Informação", of(8, 0)..of(12, 0)),
- Subject(THURSDAY, "ACH2167", "Computação Sônica", of(19, 0)..of(22, 45))
- )
- }
+ answer shouldBe setOf(
+ Subject(MONDAY, "ACH2017", "Projeto Supervisionado ou de Graduação I", of(12, 0)..of(13, 0)),
+ Subject(WEDNESDAY, "ACH2076", "Segurança da Informação", of(8, 0)..of(12, 0)),
+ Subject(THURSDAY, "ACH2167", "Computação Sônica", of(19, 0)..of(22, 45))
+ )
+ }
- should("Return the right subjects (Test 2)") {
- val answer = target.get(getenv("USP_USER_2"), getenv("USP_PASSWORD_2"))
+ should("Return the right subjects (Test 2)") {
+ val answer = target.get(getenv("USP_USER_2"), getenv("USP_PASSWORD_2"))
- answer shouldBe setOf(
- Subject(MONDAY, "PMT3100", "Fundamentos de Ciência e Engenharia dos Materiais", of(15, 0)..of(16, 40)),
- Subject(WEDNESDAY, "MAT2454", "Cálculo Diferencial e Integral II", of(11, 10)..of(12, 50)),
- Subject(FRIDAY, "MAT2454", "Cálculo Diferencial e Integral II", of(11, 10)..of(12, 50)),
- Subject(SATURDAY, "PCS3617", "Estágio Cooperativo I", of(14, 0)..of(15, 40))
- )
- }
+ answer shouldBe setOf(
+ Subject(MONDAY, "PMT3100", "Fundamentos de Ciência e Engenharia dos Materiais", of(15, 0)..of(16, 40)),
+ Subject(WEDNESDAY, "MAT2454", "Cálculo Diferencial e Integral II", of(11, 10)..of(12, 50)),
+ Subject(FRIDAY, "MAT2454", "Cálculo Diferencial e Integral II", of(11, 10)..of(12, 50)),
+ Subject(SATURDAY, "PCS3617", "Estágio Cooperativo I", of(14, 0)..of(15, 40))
+ )
+ }
})