Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .azure/container-group.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
apiVersion: "2021-10-01"
location: uk south
name: bottomly-live
properties:
containers:
- name: bottomly
properties:
image: ${IMAGE}
ports:
- port: 80
protocol: TCP
resources:
requests:
cpu: 0.5
memoryInGB: 0.5
environmentVariables:
- name: bottomly_giphy_api_key
value: ${GIPHY_API_KEY}
- name: bottomly_brave_api_key
value: ${BRAVE_API_KEY}
- name: bottomly_slack_bot_token
secureValue: ${SLACK_TOKEN}
- name: bottomly_slack_app_token
secureValue: ${SLACK_APP_TOKEN}
- name: bottomly_prefix
value: ${PREFIX}
- name: bottomly_env
value: ${ENV}
- name: ConnectionStrings__mongodb
secureValue: ${MONGO_CONN_STR}
- name: bottomly_github_token
secureValue: ${RELEASE_HISTORY_TOKEN}
- name: APPLICATIONINSIGHTS_CONNECTION_STRING
value: ${APPINSIGHTS_CONN_STR}
- name: ConnectionStrings__bottomlymodel
secureValue: "Endpoint=http://localhost:11434;Key=${OLLAMA_API_KEY}"
- name: EnableLlm
value: "true"
- name: ollama
properties:
image: ollama/ollama:latest
resources:
requests:
cpu: 0.5
memoryInGB: 0.5
environmentVariables:
- name: OLLAMA_API_KEY
secureValue: ${OLLAMA_API_KEY}
imageRegistryCredentials:
- server: ghcr.io
username: ${REGISTRY_USERNAME}
password: ${REGISTRY_PASSWORD}
ipAddress:
type: Public
dnsNameLabel: ${DNS_NAME_LABEL}
ports:
- port: 80
protocol: TCP
osType: Linux
restartPolicy: Always
tags: {}
type: Microsoft.ContainerInstance/containerGroups
33 changes: 11 additions & 22 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
@@ -1,54 +1,43 @@
---
name: .NET Build and Test

on:
push:
branches: [ "main" ]
branches: [main]
pull_request:
branches: [ "main" ]

branches: [main]
permissions:
packages: write

jobs:
build-and-test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'

dotnet-version: 10.0.x
- name: Restore dependencies
run: dotnet restore bottomly.net.slnx

- name: Build
run: dotnet build bottomly.net.slnx --no-restore --configuration Release

- name: Test
run: dotnet test bottomly.net.slnx --no-build --configuration Release --verbosity normal
run: dotnet test bottomly.net.slnx --no-build --configuration Release --verbosity
normal
env:
BOTTOMLY_BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}

- name: Build Docker image
run: docker build -t bottomly .

- name: Login to GitHub Container Registry
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set lowercase image name
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV

- name: Push image to GitHub Container Registry
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
docker tag bottomly ghcr.io/${{ env.IMAGE_NAME }}:${{ github.sha }}
docker push ghcr.io/${{ env.IMAGE_NAME }}:${{ github.sha }}
shell: bash
run: |-
IMAGE_NAME="${GITHUB_REPOSITORY,,}"
docker tag bottomly ghcr.io/$IMAGE_NAME:${{ github.sha }}
docker push ghcr.io/$IMAGE_NAME:${{ github.sha }}
52 changes: 28 additions & 24 deletions .github/workflows/push_to_live.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
name: Push to Live
on:
workflow_run:
workflows: [ ".NET Build and Test" ]
types: [ completed ]
branches: [ main ]
name: Push to Live
workflows: [.NET Build and Test]
types: [completed]
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
Expand All @@ -28,7 +28,9 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set lowercase image name
run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
id: image_name
shell: bash
run: echo "value=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
- name: Ensure Application Insights exists
id: appinsights
run: |
Expand All @@ -47,22 +49,24 @@ jobs:
--output tsv)
echo "connection_string=$CONN_STR" >> $GITHUB_OUTPUT
- name: Deploy to Azure Container Instances
uses: azure/aci-deploy@v1
with:
resource-group: ${{ secrets.RESOURCE_GROUP }}
dns-name-label: ${{ secrets.RESOURCE_GROUP }}live${{ github.run_number }}
image: ghcr.io/${{ env.IMAGE_NAME }}:${{ github.sha }}
registry-login-server: ghcr.io
registry-username: ${{ github.actor }}
registry-password: ${{ secrets.GITHUB_TOKEN }}
name: bottomly-live
location: uk south
environment-variables: bottomly_giphy_api_key=${{ secrets.GIPHY_API_KEY }}
bottomly_brave_api_key=${{ secrets.BRAVE_API_KEY }}
bottomly_slack_bot_token=${{ secrets.SLACK_TOKEN }}
bottomly_slack_app_token=${{ secrets.SLACK_APP_TOKEN }}
bottomly_prefix=${{ secrets.PREFIX }}
bottomly_env=${{ secrets.ENV }}
ConnectionStrings__mongodb=${{ secrets.MONGO_CONN_STR }}
bottomly_github_token=${{ secrets.RELEASE_HISTORY_TOKEN }}
APPLICATIONINSIGHTS_CONNECTION_STRING=${{ steps.appinsights.outputs.connection_string }}
env:
IMAGE: ghcr.io/${{ steps.image_name.outputs.value }}:${{ github.sha }}
DNS_NAME_LABEL: ${{ secrets.RESOURCE_GROUP }}live${{ github.run_number }}
GIPHY_API_KEY: ${{ secrets.GIPHY_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }}
SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}
PREFIX: ${{ secrets.PREFIX }}
ENV: ${{ secrets.ENV }}
MONGO_CONN_STR: ${{ secrets.MONGO_CONN_STR }}
RELEASE_HISTORY_TOKEN: ${{ secrets.RELEASE_HISTORY_TOKEN }}
APPINSIGHTS_CONN_STR: ${{ steps.appinsights.outputs.connection_string }}
OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }}
REGISTRY_USERNAME: ${{ github.actor }}
REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
envsubst < .azure/container-group.yml > /tmp/resolved-container-group.yml
az container create \
--resource-group ${{ secrets.RESOURCE_GROUP }} \
--file /tmp/resolved-container-group.yml
8 changes: 7 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@
"test_*.py"
],
"cSpell.words": [
"appinsights",
"Bottomly",
"sass"
"CONN",
"creds",
"GIPHY",
"OLLAMA",
"sass",
"slnx"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ private sealed class DefaultHttpClientFactory : IHttpClientFactory
[Fact]
public async Task ExecuteAsync_EmptyInput_ReturnsEmptySearchTermErrorResult()
{
if (!CredentialsAvailable) return;
if (!CredentialsAvailable)
{
return;
}

var result = await CreateCommand().ExecuteAsync("");

Expand All @@ -65,7 +68,10 @@ public async Task ExecuteAsync_EmptyInput_ReturnsEmptySearchTermErrorResult()
[Fact]
public async Task ExecuteAsync_KnownSearchTerm_ReturnsResultWithLink()
{
if (!CredentialsAvailable) return;
if (!CredentialsAvailable)
{
return;
}

var result = await CreateCommand().ExecuteAsync("GitHub");

Expand All @@ -79,7 +85,10 @@ public async Task ExecuteAsync_KnownSearchTerm_ReturnsResultWithLink()
[Fact]
public async Task ExecuteAsync_KnownSearchTerm_ReturnsRelevantResult()
{
if (!CredentialsAvailable) return;
if (!CredentialsAvailable)
{
return;
}

var result = await CreateCommand().ExecuteAsync("Wikipedia logo");

Expand All @@ -88,4 +97,4 @@ public async Task ExecuteAsync_KnownSearchTerm_ReturnsRelevantResult()
searchResult.Link.ShouldNotBeNullOrEmpty();
searchResult.Link.ShouldStartWith("http");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ private sealed class DefaultHttpClientFactory : IHttpClientFactory
[Fact]
public async Task ExecuteAsync_EmptyInput_ReturnsEmptySearchTermErrorResult()
{
if (!CredentialsAvailable) return; // credentials not configured — skip
if (!CredentialsAvailable)
{
return; // credentials not configured — skip
}

var result = await CreateCommand().ExecuteAsync("");

Expand All @@ -65,7 +68,10 @@ public async Task ExecuteAsync_EmptyInput_ReturnsEmptySearchTermErrorResult()
[Fact]
public async Task ExecuteAsync_KnownSearchTerm_ReturnsResultWithLink()
{
if (!CredentialsAvailable) return; // credentials not configured — skip
if (!CredentialsAvailable)
{
return; // credentials not configured — skip
}

var result = await CreateCommand().ExecuteAsync("GitHub");

Expand All @@ -79,7 +85,10 @@ public async Task ExecuteAsync_KnownSearchTerm_ReturnsResultWithLink()
[Fact]
public async Task ExecuteAsync_KnownSearchTerm_ReturnsRelevantResult()
{
if (!CredentialsAvailable) return; // credentials not configured — skip
if (!CredentialsAvailable)
{
return; // credentials not configured — skip
}

var result = await CreateCommand().ExecuteAsync("Wikipedia");

Expand Down
2 changes: 1 addition & 1 deletion Bottomly.Tests/Commands/RegSearchCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,4 @@ public async Task ExecuteAsync_HtmlWithError_ReturnsErrorText()

result.ShouldBe("Vehicle not found");
}
}
}
2 changes: 1 addition & 1 deletion Bottomly.Tests/Infrastructure/MongoDbCollection.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Bottomly.Tests.Infrastructure;

[CollectionDefinition("MongoDB")]
public class MongoDbCollection : ICollectionFixture<MongoDbFixture>;
public class MongoDbCollection : ICollectionFixture<MongoDbFixture>;
2 changes: 1 addition & 1 deletion Bottomly.Tests/Infrastructure/MongoDbFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ public async Task InitializeAsync()
}

public async Task DisposeAsync() => await _container.DisposeAsync();
}
}
2 changes: 1 addition & 1 deletion Bottomly.Tests/LlmBot/LlmClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,4 @@ public async Task Respond_PassesPromptToClient()
It.IsAny<ChatOptions?>(),
It.IsAny<CancellationToken>()), Times.Once());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ private static Karma MakeKarma(
private async Task AddKarmaMultiple(string recipient, KarmaType type, int count)
{
for (var i = 0; i < count; i++)
{
await _sut.AddAsync(MakeKarma(recipient, "giver", type));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,4 @@ public async Task UpdateInfoAsync_WhenUsernameDoesNotExist_DoesNotThrow()
await Should.NotThrowAsync(() =>
_sut.UpdateInfoAsync("ghost", "Ghost", Gender.Unknown, SassLevel.None, ""));
}
}
}
2 changes: 1 addition & 1 deletion Bottomly.Tests/Slack/EventHandlers/RegHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,4 @@ public async Task HandleAsync_HelpEvent_SendsHelpMessage()
_mockBroker.Verify(b => b.SendMessageAsync(
It.Is<string>(s => s.Contains("Test")), "C1", null), Times.Once());
}
}
}
2 changes: 1 addition & 1 deletion Bottomly.Tests/Slack/MemberlistPopulatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,4 @@ public async Task PopulateMembers_NotSeeded_FetchesAndSavesMembers()
result.ShouldContain(m => m.Username == "bob" && m.SlackId == "U2");
_mockRepo.Verify(r => r.AddAsync(It.Is<IEnumerable<Member>>(members => members.Count() == 2)), Times.Once());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@ public async Task ExecuteAsync_ValidEvent_AddsNewMember()
_mockRepo.Verify(r => r.AddAsync(It.Is<Member>(m =>
m.SlackId == "U1" && m.Username == "alice")), Times.Once());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,4 @@ private void SetupConversationHistory(string channel, List<MessageEvent> message
private void SetupMembers(List<Member> members) =>
_mockMemberRepo.Setup(r => r.GetBySlackIdsAsync(It.IsAny<IEnumerable<string>>()))
.ReturnsAsync(members);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ public void CreateFromSlackMessage_UnknownUser_FallsBackToSlackId()
inputMessage.Username.ShouldBe("U_UNKNOWN");
inputMessage.Text.ShouldBe("hey");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ public void ToSlackResponse_LlmUnknownErrorResponse_ReturnsFallbackMessage()

result.ShouldNotBeNullOrEmpty();
}
}
}
2 changes: 1 addition & 1 deletion Bottomly.Tests/Slack/SlackMessageBrokerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,4 @@ public async Task SendDmAsync_ValidText_OpensConversationAndPosts()
_mockChat.Verify(c => c.PostMessage(It.Is<Message>(m =>
m.Channel == "D_CHANNEL" && m.Text == "Private message")), Times.Once());
}
}
}
2 changes: 1 addition & 1 deletion Bottomly.Tests/Slack/SlackWorkerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,4 +304,4 @@ public async Task SlackReactionEventDispatcher_DelegatesToWorker()
var dispatcher = new SlackReactionEventDispatcher(worker);
await dispatcher.Handle(new ReactionAdded { Reaction = "joy" });
}
}
}
Loading
Loading