Commit 70de9989 authored by Petar Hristov's avatar Petar Hristov 💬 Committed by Sirieam Marie Hunke
Browse files

Fix: Resource mapping with missing type options

parent b59ba0e3
Loading
Loading
Loading
Loading
+6 −4
Original line number Diff line number Diff line
@@ -89,7 +89,9 @@ const configuration = new MapperConfiguration((cfg) => {
        DisciplineDto2DisciplineForResourceManipulationDto,
      ),
    resourceTypeOptions: (opt) =>
      opt.mapFromUsing(
      opt
        .preCondition((e) => e.type?.options !== undefined)
        .mapFromUsing(
          (e) => e.type?.options,
          ResourceTypeOptionsDto2ResourceTypeOptionsForUpdateDto,
        ),
+178 −0
Original line number Diff line number Diff line
/* Testing imports */
import { type Wrapper, createLocalVue, mount } from "@vue/test-utils";
import { createTestingPinia } from "@pinia/testing";

/* Vue i18n */
import i18n, { def } from "@/plugins/vue-i18n";
import { ProjectI18nMessages } from "@/modules/project/i18n/index";
i18n.availableLocales.forEach((locale) => {
  i18n.setLocaleMessage(locale, def[locale]); // default locale messages
  i18n.mergeLocaleMessage(locale, ProjectI18nMessages[locale]); // append the locale messages for the component
});

/* Pinia */
import { PiniaVuePlugin } from "pinia";

/* Tested Component */
import Quota from "./Quota.vue";
import VueRouter from "vue-router";
import { routes } from "@/router";

import Vue from "vue";

import { getTestShibbolethUserState } from "@/data/mockup/testUser";
import { getTestResourceState } from "@/data/mockup/testResource";
import { testProjectState } from "@/data/mockup/testProject";
import { useProjectStore } from "../store";
import {
  ProjectQuotaDto,
  ResourceQuotaDto,
} from "@coscine/api-client/dist/types/Coscine.Api";
import {
  ExtendedResourceTypeInformationDto,
  QuotaUnit,
} from "@/modules/resource/types";

// Define the Vue instance type (computed properties)
interface QuotaPageComponent extends Vue {
  resourceTypesInformation: ExtendedResourceTypeInformationDto[] | null;
  updateResourceTypeProjectQuota: () => Promise<void>;
  updateResourceQuota: () => Promise<void>;
}

/* Create a local Vue instance */
const localVue = createLocalVue();
localVue.use(PiniaVuePlugin);
localVue.use(VueRouter);
const router = new VueRouter({ routes: routes });

describe("Quota.vue", async () => {
  let wrapper: Wrapper<QuotaPageComponent>;

  // Create a mocked pinia instance with initial state
  const testingPinia = createTestingPinia({
    createSpy: vitest.fn,
    initialState: {
      project: testProjectState,
      resource: await getTestResourceState(),
      user: getTestShibbolethUserState(),
    },
  });

  // Mock the API calls
  const projectStore = useProjectStore(testingPinia);
  vi.mocked(projectStore.updateResourceTypeProjectQuota).mockReturnValue(
    Promise.resolve(true),
  );
  vi.mocked(projectStore.updateResourceQuota).mockReturnValue(
    Promise.resolve(true),
  );

  beforeEach(() => {
    // shallowMount does not work here!
    wrapper = mount(Quota as unknown as typeof Vue, {
      pinia: testingPinia,
      router,
      i18n,
      localVue,

      computed: {
        resourceTypesInformation() {
          return [
            {
              id: "rt1",
              iDisplayName: "Resource Type 1",
              isQuotaAdjustable: true,
              isQuotaAvailable: true,
              isEnabled: true,
            },
            {
              id: "rt2",
              iDisplayName: "Resource Type 2",
              isQuotaAdjustable: true,
              isQuotaAvailable: true,
              isEnabled: true,
            },
          ] as ExtendedResourceTypeInformationDto[] | null;
        },
      },
    }) as Wrapper<QuotaPageComponent>;
  });

  test("Should populate the resource types dropdown correctly.", async () => {
    await wrapper.vm.$nextTick();

    const options = wrapper
      .findComponent({ ref: "resourceTypeSelect" })
      .findAll("option");
    expect(options.length).toBeGreaterThan(1);
    expect(options.at(1).text()).toContain("Resource Type 1");
    expect(options.at(2).text()).toContain("Resource Type 2");
  });

  test("Should adjust the project resource type quota", async () => {
    await wrapper.setData({
      selectedQuotas: {
        allocated: { value: 50, unit: QuotaUnit.GibiByte },
        totalReserved: { value: 10, unit: QuotaUnit.GibiByte },
      } as ProjectQuotaDto | undefined,
      selectedResourceTypeInformation: { isQuotaAdjustable: true },
    });

    await wrapper.vm.$nextTick();

    const slider = wrapper.find("#projectQuotaSlider");
    slider.setValue(20);
    await slider.trigger("change");

    expect(projectStore.updateResourceTypeProjectQuota).toHaveBeenCalledOnce();
  });

  test("Should adjust the resource quota", async () => {
    await wrapper.setData({
      resources: [
        { id: "res1", type: { id: "rt1" }, displayName: "Resource 1" },
      ],
      resourceTypesQuotas: [
        {
          allocated: { value: 50, unit: QuotaUnit.GibiByte },
          totalReserved: { value: 10, unit: QuotaUnit.GibiByte },
          maximum: { value: 100, unit: QuotaUnit.GibiByte },
          resourceType: { id: "rt1" },
          resourceQuotas: [
            {
              reserved: { value: 2, unit: QuotaUnit.GibiByte },
              used: { value: 1, unit: QuotaUnit.GibiByte },
              usedPercentage: 0.5,
              resource: { id: "res1" },
            },
          ] as ResourceQuotaDto[],
        },
      ] as ProjectQuotaDto[],
      selectedQuotas: {
        allocated: { value: 50, unit: QuotaUnit.GibiByte },
        totalReserved: { value: 10, unit: QuotaUnit.GibiByte },
        maximum: { value: 100, unit: QuotaUnit.GibiByte },
        resourceType: { id: "rt1" },
        totalUsed: { value: 1 },
        projectId: "p1",
        resourceQuotas: [
          {
            reserved: { value: 2, unit: QuotaUnit.GibiByte },
            used: { value: 1, unit: QuotaUnit.GibiByte },
            usedPercentage: 0.5,
            resource: { id: "res1" },
          },
        ],
      } as ProjectQuotaDto | undefined,
    });

    await wrapper.vm.$nextTick();

    const slider = wrapper.find("#resourceQuotaSlider-res1");
    slider.setValue(15);
    await slider.trigger("change");

    expect(projectStore.updateResourceQuota).toHaveBeenCalledOnce();
  });
});
+3 −1
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@
          <b-col>
            {{ $t("page.quota.resourceTypeLabel") }}
            <b-form-select
              ref="resourceTypeSelect"
              v-model="selectedResourceTypeId"
              :options="resourceTypesInformation"
              value-field="id"
@@ -94,6 +95,7 @@
            <!-- v-model unit must be GiB -->
            <b-form-input
              v-if="selectedQuotas?.allocated"
              id="projectQuotaSlider"
              v-model="selectedQuotas.allocated.value"
              type="range"
              :min="Math.max(toGiB(selectedQuotas.totalReserved), 1)"
@@ -236,7 +238,7 @@
              </span>
              <!-- Resource Quota Slider -->
              <b-form-input
                id="resourceQuotaSlider"
                :id="'resourceQuotaSlider-' + data.item.resource.id"
                v-model="data.item.reserved.value"
                type="range"
                :min="minGiB(data.item.resource.id)"